diff --git a/src/kits/Jamfile b/src/kits/Jamfile index b16c60c09c..cea560bceb 100644 --- a/src/kits/Jamfile +++ b/src/kits/Jamfile @@ -114,6 +114,7 @@ SubInclude OBOS_TOP src kits screensaver ; SubInclude OBOS_TOP src kits support ; SubInclude OBOS_TOP src kits textencoding ; SubInclude OBOS_TOP src kits translation ; +SubInclude OBOS_TOP src kits tracker ; SubInclude OBOS_TOP src kits device ; SubInclude OBOS_TOP src kits game ; SubInclude OBOS_TOP src kits network ; diff --git a/src/kits/tracker/AUTHORS b/src/kits/tracker/AUTHORS new file mode 100644 index 0000000000..183f215bc1 --- /dev/null +++ b/src/kits/tracker/AUTHORS @@ -0,0 +1,10 @@ +Authors (mostly in chronological order): + +Steve Horowitz, +Pavel Cisler, +Peter Potrebic, +Jeff Bush, +Robert Chinn, +Doug Fulton, +Kenny Carruthers, +Dianne Hackborn \ No newline at end of file diff --git a/src/kits/tracker/AboutBox.cpp b/src/kits/tracker/AboutBox.cpp new file mode 100644 index 0000000000..024b4d83fc --- /dev/null +++ b/src/kits/tracker/AboutBox.cpp @@ -0,0 +1,105 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// These classes are required for binary compatibility under PPC, because +// libtracker used to export the symbols. Note that nothing actually +// uses them. + +#if __POWERPC__ + +#include +#include + +class AboutWindow : public BWindow { +public: + AboutWindow() + : BWindow(BRect(), "", B_UNTYPED_WINDOW, 0) + { + } + + ~AboutWindow() + { + } + + static void RunAboutWindow() + { + } +}; + +class AboutView : public BView { +public: + AboutView(BRect frame, const char *name) + : BView(frame, name, 0, 0) + { + } + + virtual ~AboutView() + { + } + + virtual void AttachedToWindow() + { + } + + virtual void Pulse() + { + } + + virtual void Draw(BRect) + { + } + + void UpdateInfo() + { + } + + virtual void MouseDown(BPoint) + { + } +}; + +namespace BPrivate { + +void +do_not_call_me_i_am_only_here_to_get_these_symbols(AboutWindow **win, AboutView **view) +{ + AboutWindow rwin; + AboutView rview(BRect(0, 0, 2, 3), ""); + *win = &rwin; + *view = &rview; +} + +} + +#endif diff --git a/src/kits/tracker/AttributeStream.cpp b/src/kits/tracker/AttributeStream.cpp new file mode 100644 index 0000000000..13cff327e3 --- /dev/null +++ b/src/kits/tracker/AttributeStream.cpp @@ -0,0 +1,770 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include +#include "AttributeStream.h" + +// ToDo: +// lazy Rewind from Drive, only if data is available +// BMessage node +// partial feeding (part, not the whole buffer) + +AttributeInfo::AttributeInfo(const AttributeInfo &cloneThis) + : fName(cloneThis.fName), + fInfo(cloneThis.fInfo) + +{ +} + +AttributeInfo::AttributeInfo(const char *name, attr_info info) + : fName(name), + fInfo(info) +{ +} + + +AttributeInfo::AttributeInfo(const char *name, uint32 type, off_t size) + : fName(name) +{ + fInfo.size = size; + fInfo.type = type; +} + + +const char * +AttributeInfo::Name() const +{ + return fName.String(); +} + +uint32 +AttributeInfo::Type() const +{ + return fInfo.type; +} + +off_t +AttributeInfo::Size() const +{ + return fInfo.size; +} + + +void +AttributeInfo::SetTo(const AttributeInfo &attr) +{ + fName = attr.fName; + fInfo = attr.fInfo; +} + +void +AttributeInfo::SetTo(const char *name, attr_info info) +{ + fName = name; + fInfo = info; +} + +void +AttributeInfo::SetTo(const char *name, uint32 type, off_t size) +{ + fName = name; + fInfo.type = type; + fInfo.size = size; +} + + +AttributeStreamNode::AttributeStreamNode() + : fReadFrom(NULL), + fWriteTo(NULL) +{ +} + + +AttributeStreamNode::~AttributeStreamNode() +{ + Detach(); +} + +AttributeStreamNode & +AttributeStreamNode::operator<<(AttributeStreamNode &source) +{ + fReadFrom = &source; + fReadFrom->fWriteTo = this; + if (fReadFrom->CanFeed()) + fReadFrom->Start(); + + return source; +} + +void +AttributeStreamNode::Rewind() +{ + if (fReadFrom) + fReadFrom->Rewind(); +} + +void +AttributeStreamFileNode::MakeEmpty() +{ + TRESPASS(); +} + +off_t +AttributeStreamNode::Contains(const char *name, uint32 type) +{ + if (!fReadFrom) + return 0; + + return fReadFrom->Contains(name, type); +} + + +off_t +AttributeStreamNode::Read(const char *name, const char *foreignName, uint32 type, + off_t size, void *buffer, void (*swapFunc)(void *)) +{ + if (!fReadFrom) + return 0; + + return fReadFrom->Read(name, foreignName, type, size, buffer, swapFunc); +} + +off_t +AttributeStreamNode::Write(const char *name, const char *foreignName, uint32 type, + off_t size, const void *buffer) +{ + if (!fWriteTo) + return 0; + + return fWriteTo->Write(name, foreignName, type, size, buffer); +} + +bool +AttributeStreamNode::Drive() +{ + ASSERT(CanFeed()); + if (!fReadFrom) + return false; + + Rewind(); + return true; +} + +const AttributeInfo * +AttributeStreamNode::Next() +{ + if (fReadFrom) + return fReadFrom->Next(); + + return NULL; +} + +const char * +AttributeStreamNode::Get() +{ + ASSERT(fReadFrom); + if (!fReadFrom) + return NULL; + + return fReadFrom->Get(); +} + +bool +AttributeStreamNode::Fill(char *buffer) const +{ + ASSERT(fReadFrom); + return fReadFrom->Fill(buffer); +} + +bool +AttributeStreamNode::Start() +{ + if (!fWriteTo) + // we are at the head of the stream, start drivin' + return Drive(); + + return fWriteTo->Start(); +} + +void +AttributeStreamNode::Detach() +{ + AttributeStreamNode *tmpFrom = fReadFrom; + AttributeStreamNode *tmpTo = fWriteTo; + fReadFrom = NULL; + fWriteTo = NULL; + + if (tmpFrom) + tmpFrom->Detach(); + if (tmpTo) + tmpTo->Detach(); +} + + +AttributeStreamFileNode::AttributeStreamFileNode() + : fNode(NULL) +{ +} + + +AttributeStreamFileNode::AttributeStreamFileNode(BNode *node) + : fNode(node) +{ + ASSERT(fNode); +} + +void +AttributeStreamFileNode::Rewind() +{ + _inherited::Rewind(); + fNode->RewindAttrs(); +} + +void +AttributeStreamFileNode::SetTo(BNode *node) +{ + fNode = node; +} + + +off_t +AttributeStreamFileNode::Contains(const char *name, uint32 type) +{ + ASSERT(fNode); + attr_info info; + if (fNode->GetAttrInfo(name, &info) != B_OK) + return 0; + + if (info.type != type) + return 0; + + return info.size; +} + +off_t +AttributeStreamFileNode::Read(const char *name, const char *foreignName, uint32 type, + off_t size, void *buffer, void (*swapFunc)(void *)) +{ + if (name && fNode->ReadAttr(name, type, 0, buffer, (size_t)size) == size) + return size; + + // didn't find the attribute under the native name, try the foreign name + if (foreignName && fNode->ReadAttr(foreignName, type, 0, buffer, (size_t)size) == size) { + // foreign attribute, swap the data + if (swapFunc) + (swapFunc)(buffer); + return size; + } + return 0; +} + +off_t +AttributeStreamFileNode::Write(const char *name, const char *foreignName, uint32 type, + off_t size, const void *buffer) +{ + ASSERT(fNode); + ASSERT(dynamic_cast(fNode)); + off_t result = fNode->WriteAttr(name, type, 0, buffer, (size_t)size); + if (result == size && foreignName) + // the write operation worked fine, remove the foreign attribute + // to not let stale data hang around + fNode->RemoveAttr(foreignName); + + return result; +} + +bool +AttributeStreamFileNode::Drive() +{ + ASSERT(fNode); + if (!_inherited::Drive()) + return false; + + const AttributeInfo *attr; + while ((attr = fReadFrom->Next()) != 0) { + const char *data = fReadFrom->Get(); + off_t result = fNode->WriteAttr(attr->Name(), attr->Type(), 0, + data, (size_t)attr->Size()); + if (result < attr->Size()) + return true; + } + return true; +} + +const char * +AttributeStreamFileNode::Get() +{ + ASSERT(fNode); + TRESPASS(); + return NULL; +} + +bool +AttributeStreamFileNode::Fill(char *buffer) const +{ + ASSERT(fNode); + return fNode->ReadAttr(fCurrentAttr.Name(), fCurrentAttr.Type(), 0, buffer, + (size_t)fCurrentAttr.Size()) == (ssize_t)fCurrentAttr.Size(); +} + +const AttributeInfo * +AttributeStreamFileNode::Next() +{ + ASSERT(fNode); + ASSERT(!fReadFrom); + char attrName[256]; + if (fNode->GetNextAttrName(attrName) != B_OK) + return NULL; + + attr_info info; + if (fNode->GetAttrInfo(attrName, &info) != B_OK) + return NULL; + + fCurrentAttr.SetTo(attrName, info); + return &fCurrentAttr; +} + + +AttributeStreamMemoryNode::AttributeStreamMemoryNode() + : fAttributes(5, true), + fCurrentIndex(-1) +{ +} + +void +AttributeStreamMemoryNode::MakeEmpty() +{ + fAttributes.MakeEmpty(); +} + +void +AttributeStreamMemoryNode::Rewind() +{ + _inherited::Rewind(); + fCurrentIndex = -1; +} + +int32 +AttributeStreamMemoryNode::Find(const char *name, uint32 type) const +{ + int32 count = fAttributes.CountItems(); + for (int32 index = 0; index < count; index++) + if (strcmp(fAttributes.ItemAt(index)->fAttr.Name(), name) == 0 + && fAttributes.ItemAt(index)->fAttr.Type() == type) + return index; + + return -1; +} + +off_t +AttributeStreamMemoryNode::Contains(const char *name, uint32 type) +{ + int32 index = Find(name, type); + if (index < 0) + return 0; + return fAttributes.ItemAt(index)->fAttr.Size(); +} + + +off_t +AttributeStreamMemoryNode::Read(const char *name, const char *DEBUG_ONLY(foreignName), + uint32 type, off_t bufferSize, void *buffer, void (*DEBUG_ONLY(swapFunc))(void *)) +{ + ASSERT(!foreignName); + ASSERT(!swapFunc); + + AttrNode *attrNode = NULL; + + int32 index = Find(name, type); + if (index < 0) { + if (!fReadFrom) + return 0; + off_t size = fReadFrom->Contains(name, type); + if (!size) + return 0; + + attrNode = BufferingGet(name, type, size); + if (!attrNode) + return 0; + } else + attrNode = fAttributes.ItemAt(index); + + if (attrNode->fAttr.Size() > bufferSize) + return 0; + + memcpy(buffer, attrNode->fData, (size_t)attrNode->fAttr.Size()); + return attrNode->fAttr.Size(); +} + +off_t +AttributeStreamMemoryNode::Write(const char *name, const char *, uint32 type, + off_t size, const void *buffer) +{ + char *newBuffer = new char[size]; + memcpy(newBuffer, buffer, (size_t)size); + + AttrNode *attrNode = new AttrNode(name, type, size, newBuffer); + fAttributes.AddItem(attrNode); + return size; +} + +bool +AttributeStreamMemoryNode::Drive() +{ + if (!_inherited::Drive()) + return false; + + while (BufferingGet()) + ; + + return true; +} + +AttributeStreamMemoryNode::AttrNode * +AttributeStreamMemoryNode::BufferingGet(const char *name, uint32 type, off_t size) +{ + char *newBuffer = new char[size]; + if (!fReadFrom->Fill(newBuffer)) { + delete newBuffer; + return NULL; + } + + AttrNode *attrNode = new AttrNode(name, type, size, newBuffer); + fAttributes.AddItem(attrNode); + return fAttributes.LastItem(); +} + + +AttributeStreamMemoryNode::AttrNode * +AttributeStreamMemoryNode::BufferingGet() +{ + if (!fReadFrom) + return NULL; + + const AttributeInfo *attr = fReadFrom->Next(); + if (!attr) + return NULL; + + return BufferingGet(attr->Name(), attr->Type(), attr->Size()); +} + +const AttributeInfo * +AttributeStreamMemoryNode::Next() +{ + if (fReadFrom) + // the buffer is in the middle of the stream, get + // one buffer at a time + BufferingGet(); + + if (fCurrentIndex + 1 >= fAttributes.CountItems()) + return NULL; + + return &fAttributes.ItemAt(++fCurrentIndex)->fAttr; +} + +const char * +AttributeStreamMemoryNode::Get() +{ + ASSERT(fCurrentIndex < fAttributes.CountItems()); + return fAttributes.ItemAt(fCurrentIndex)->fData; +} + +bool +AttributeStreamMemoryNode::Fill(char *buffer) const +{ + ASSERT(fCurrentIndex < fAttributes.CountItems()); + memcpy(buffer, fAttributes.ItemAt(fCurrentIndex)->fData, + (size_t)fAttributes.ItemAt(fCurrentIndex)->fAttr.Size()); + + return true; +} + + +AttributeStreamTemplateNode::AttributeStreamTemplateNode(const AttributeTemplate * + attrTemplates, int32 count) + : fAttributes(attrTemplates), + fCurrentIndex(-1), + fCount(count) +{ +} + +off_t +AttributeStreamTemplateNode::Contains(const char *name, uint32 type) +{ + int32 index = Find(name, type); + if (index < 0) + return 0; + + return fAttributes[index].fSize; +} + +void +AttributeStreamTemplateNode::Rewind() +{ + fCurrentIndex = -1; +} + +const AttributeInfo * +AttributeStreamTemplateNode::Next() +{ + if (fCurrentIndex + 1 >= fCount) + return NULL; + + ++fCurrentIndex; + + fCurrentAttr.SetTo(fAttributes[fCurrentIndex].fAttributeName, + fAttributes[fCurrentIndex].fAttributeType, fAttributes[fCurrentIndex].fSize); + + return &fCurrentAttr; +} + +const char * +AttributeStreamTemplateNode::Get() +{ + ASSERT(fCurrentIndex < fCount); + return fAttributes[fCurrentIndex].fBits; +} + +bool +AttributeStreamTemplateNode::Fill(char *buffer) const +{ + ASSERT(fCurrentIndex < fCount); + memcpy(buffer, fAttributes[fCurrentIndex].fBits, (size_t)fAttributes[fCurrentIndex].fSize); + + return true; +} + +int32 +AttributeStreamTemplateNode::Find(const char *name, uint32 type) const +{ + for (int32 index = 0; index < fCount; index++) + if (fAttributes[index].fAttributeType == type && + strcmp(name, fAttributes[index].fAttributeName) == 0) + return index; + + return -1; +} + +bool +AttributeStreamFilterNode::Reject(const char *, uint32 , off_t ) +{ + // simple pass everything filter + return false; +} + +const AttributeInfo * +AttributeStreamFilterNode::Next() +{ + if (!fReadFrom) + return NULL; + + for (;;) { + const AttributeInfo *attr = fReadFrom->Next(); + if (!attr) + break; + + if (!Reject(attr->Name(), attr->Type(), attr->Size())) + return attr; + } + return NULL; +} + +off_t +AttributeStreamFilterNode::Contains(const char *name, uint32 type) +{ + if (!fReadFrom) + return 0; + + off_t size = fReadFrom->Contains(name, type); + + if (!Reject(name, type, size)) + return size; + + return 0; +} + +off_t +AttributeStreamFilterNode::Read(const char *name, const char *foreignName, uint32 type, + off_t size, void *buffer, void (*swapFunc)(void *)) +{ + if (!fReadFrom) + return 0; + + if (!Reject(name, type, size)) + return fReadFrom->Read(name, foreignName, type, size, buffer, swapFunc); + + return 0; +} + +off_t +AttributeStreamFilterNode::Write(const char *name, const char *foreignName, uint32 type, + off_t size, const void *buffer) +{ + if (!fWriteTo) + return 0; + + if (!Reject(name, type, size)) + return fWriteTo->Write(name, foreignName, type, size, buffer); + + return size; +} + + +NamesToAcceptAttrFilter::NamesToAcceptAttrFilter(const char **nameList) + : fNameList(nameList) +{ +} + +bool +NamesToAcceptAttrFilter::Reject(const char *name, uint32 , off_t ) +{ + for (int32 index = 0; ;index++) { + if (!fNameList[index]) + break; + + if (strcmp(name, fNameList[index]) == 0) { +// PRINT(("filter passing through %s\n", name)); + return false; + } + } +// PRINT(("filter rejecting %s\n", name)); + return true; +} + + +SelectiveAttributeTransformer::SelectiveAttributeTransformer(const char *attributeName, + bool (*transformFunc)(const char * , uint32 , off_t, void *, void *), void *params) + : fAttributeNameToTransform(attributeName), + fTransformFunc(transformFunc), + fTransformParams(params), + fTransformedBuffers(10, false) +{ +} + + +SelectiveAttributeTransformer::~SelectiveAttributeTransformer() +{ + for (int32 index = fTransformedBuffers.CountItems() - 1; index >= 0; index--) + delete [] fTransformedBuffers.ItemAt(index); +} + +void +SelectiveAttributeTransformer::Rewind() +{ + for (int32 index = fTransformedBuffers.CountItems() - 1; index >= 0; index--) + delete [] fTransformedBuffers.ItemAt(index); + + fTransformedBuffers.MakeEmpty(); +} + + +off_t +SelectiveAttributeTransformer::Read(const char *name, const char *foreignName, + uint32 type, off_t size, void *buffer, void (*swapFunc)(void *)) +{ + if (!fReadFrom) + return 0; + + off_t result = fReadFrom->Read(name, foreignName, type, size, buffer, swapFunc); + + if (WillTransform(name, type, size, (const char *)buffer)) + ApplyTransformer(name, type, size, (char *)buffer); + + return result; +} + +bool +SelectiveAttributeTransformer::WillTransform(const char *name, uint32 , off_t , + const char *) const +{ + return strcmp(name, fAttributeNameToTransform) == 0; +} + +bool +SelectiveAttributeTransformer::ApplyTransformer(const char *name, uint32 type, off_t size, + char *data) +{ + return (fTransformFunc)(name, type, size, data, fTransformParams); +} + +char * +SelectiveAttributeTransformer::CopyAndApplyTransformer(const char *name, uint32 type, + off_t size, const char *data) +{ + char *result = NULL; + if (data) { + result = new char[size]; + memcpy(result, data, (size_t)size); + } + + if (!(fTransformFunc)(name, type, size, result, fTransformParams)) { + delete [] result; + return NULL; + } + return result; +} + +const AttributeInfo * +SelectiveAttributeTransformer::Next() +{ + const AttributeInfo *result = fReadFrom->Next(); + if (!result) + return NULL; + + fCurrentAttr.SetTo(*result); + return result; +} + +const char * +SelectiveAttributeTransformer::Get() +{ + if (!fReadFrom) + return NULL; + + const char *result = fReadFrom->Get(); + + if (!WillTransform(fCurrentAttr.Name(), fCurrentAttr.Type(), fCurrentAttr.Size(), result)) + return result; + + char *transformedData = CopyAndApplyTransformer(fCurrentAttr.Name(), fCurrentAttr.Type(), + fCurrentAttr.Size(), result); + + // enlist for proper disposal when our job is done + if (transformedData) { + fTransformedBuffers.AddItem(transformedData); + return transformedData; + } + + return result; +} diff --git a/src/kits/tracker/AttributeStream.h b/src/kits/tracker/AttributeStream.h new file mode 100644 index 0000000000..318ff3d112 --- /dev/null +++ b/src/kits/tracker/AttributeStream.h @@ -0,0 +1,444 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// attribute streams allow copying/filtering/transformation of attributes +// between file and/or memory nodes +// +// for example one can use constructs of nodes like: +// +// destinationNode << transformer << buffer << filter << sourceNode +// +// transformer may for instance perform endian-swapping or offsetting of a B_RECT attribute +// filter may withold certain attributes +// buffer is a memory allocated snapshot of attributes, may be repeatedly streamed into +// other files, buffers +// +// In addition to the whacky (but usefull) << syntax, calls like Read, Write are also +// available + + +#ifndef __ATTRIBUTE_STREAM__ +#define __ATTRIBUTE_STREAM__ + +#include +#include +#include +#include + +#include + +#include "ObjectList.h" + +namespace BPrivate { + +struct AttributeTemplate { + // used for read-only attribute source + const char *fAttributeName; + uint32 fAttributeType; + off_t fSize; + const char *fBits; +}; + + +class AttributeInfo { + // utility class for internal attribute description +public: + AttributeInfo() + {} + AttributeInfo(const AttributeInfo &); + AttributeInfo(const char *, attr_info); + AttributeInfo(const char *, uint32, off_t); + + void SetTo(const AttributeInfo &); + void SetTo(const char *, attr_info); + void SetTo(const char *, uint32, off_t); + const char *Name() const; + uint32 Type() const; + off_t Size() const; + +private: + BString fName; + attr_info fInfo; +}; + +class AttributeStreamNode { +public: + AttributeStreamNode(); + virtual ~AttributeStreamNode(); + + AttributeStreamNode &operator<<(AttributeStreamNode &source); + // workhorse call + // to the outside makes this node a part of the stream, passing on + // any data it has, gets, transforms, doesn't filter out + // + // under the hood sets up streaming into the next node; hooking + // up source and destination, forces the stream head to start streaming + + virtual void Rewind(); + // get ready to start all over again + virtual void MakeEmpty() {} + // remove any attributes the node may have + + virtual off_t Contains(const char *, uint32); + // returns size of attribute if found + + virtual off_t Read(const char *name, const char *foreignName, uint32 type, off_t size, + void *buffer, void (*swapFunc)(void *) = 0); + // read from this node + virtual off_t Write(const char *name, const char *foreignName, uint32 type, off_t size, + const void *buffer); + // write to this node + + + // work calls + virtual bool Drive(); + // node at the head of the stream makes the entire stream + // feed it + virtual const AttributeInfo *Next(); + // give me the next attribute in the stream + virtual const char *Get(); + // give me the data of the attribute in the stream that was just returned + // by Next + // assumes there is a buffering node somewhere on the way to + // the source, from which the resulting buffer is borrowed + virtual bool Fill(char *buffer) const; + // fill the buffer with data of the attribute in the stream that was just returned + // by next + // is big enough to hold the entire attribute data + + virtual bool CanFeed() const { return false; } + // return true if can work as a source for the entire stream + +private: + bool Start(); + // utility call, used to start up the stream by finding the ultimate + // target of the stream and calling Drive on it + + void Detach(); + +protected: + AttributeStreamNode *fReadFrom; + AttributeStreamNode *fWriteTo; +}; + +class AttributeStreamFileNode : public AttributeStreamNode { + // handles reading and writing attributes to and from the + // stream +public: + AttributeStreamFileNode(); + AttributeStreamFileNode(BNode *); + + virtual void MakeEmpty(); + virtual void Rewind(); + virtual off_t Contains(const char *name, uint32 type); + virtual off_t Read(const char *name, const char *foreignName, uint32 type, off_t size, + void *buffer, void (*swapFunc)(void *) = 0); + virtual off_t Write(const char *name, const char *foreignName, uint32 type, off_t size, + const void *buffer); + + void SetTo(BNode *); + + BNode *Node() + { return fNode; } + +protected: + virtual bool CanFeed() const { return true; } + + virtual bool Drive(); + // give me all the attributes, I'll write them into myself + virtual const AttributeInfo *Next(); + // return the info for the next attribute I can read for you + virtual const char *Get(); + virtual bool Fill(char *buffer) const; + +private: + AttributeInfo fCurrentAttr; + BNode *fNode; + + typedef AttributeStreamNode _inherited; +}; + +class AttributeStreamMemoryNode : public AttributeStreamNode { + // in memory attribute buffer; can be both target of writing and source + // of reading at the same time +public: + AttributeStreamMemoryNode(); + + virtual void MakeEmpty(); + virtual off_t Contains(const char *name, uint32 type); + virtual off_t Read(const char *name, const char *foreignName, uint32 type, off_t size, + void *buffer, void (*swapFunc)(void *) = 0); + virtual off_t Write(const char *name, const char *foreignName, uint32 type, off_t size, + const void *buffer); + +protected: + + virtual bool CanFeed() const { return true; } + virtual void Rewind(); + virtual bool Drive(); + virtual const AttributeInfo *Next(); + virtual const char *Get(); + virtual bool Fill(char *buffer) const; + + class AttrNode { + public: + AttrNode(const char *name, uint32 type, off_t size, char *data) + : fAttr(name, type, size), + fData(data) + { + } + + ~AttrNode() + { + delete [] fData; + } + + AttributeInfo fAttr; + char *fData; + }; + + // utility calls + virtual AttrNode *BufferingGet(); + virtual AttrNode *BufferingGet(const char *name, uint32 type, off_t size); + int32 Find(const char *name, uint32 type) const; + +private: + + BObjectList fAttributes; + int32 fCurrentIndex; + + typedef AttributeStreamNode _inherited; +}; + +class AttributeStreamTemplateNode : public AttributeStreamNode { + // in read-only memory attribute source + // can only be used as a source for Next and Get +public: + AttributeStreamTemplateNode(const AttributeTemplate *, int32 count); + + virtual off_t Contains(const char *name, uint32 type); + +protected: + + virtual bool CanFeed() const { return true; } + virtual void Rewind(); + virtual const AttributeInfo *Next(); + virtual const char *Get(); + virtual bool Fill(char *buffer) const; + + int32 Find(const char *name, uint32 type) const; + +private: + AttributeInfo fCurrentAttr; + const AttributeTemplate *fAttributes; + int32 fCurrentIndex; + int32 fCount; + + typedef AttributeStreamNode _inherited; +}; + +class AttributeStreamFilterNode : public AttributeStreamNode { + // filter node may not pass thru specified attributes +public: + AttributeStreamFilterNode() + {} + virtual off_t Contains(const char *name, uint32 type); + virtual off_t Read(const char *name, const char *foreignName, uint32 type, off_t size, + void *buffer, void (*swapFunc)(void *) = 0); + virtual off_t Write(const char *name, const char *foreignName, uint32 type, off_t size, + const void *buffer); + +protected: + virtual bool Reject(const char *name, uint32 type, off_t size); + // override to implement filtering + virtual const AttributeInfo *Next(); + +private: + typedef AttributeStreamNode _inherited; +}; + +class NamesToAcceptAttrFilter : public AttributeStreamFilterNode { + // filter node that only passes thru attributes that match + // a list of names +public: + NamesToAcceptAttrFilter(const char **); + +protected: + virtual bool Reject(const char *name, uint32 type, off_t size); + +private: + const char **fNameList; +}; + +class SelectiveAttributeTransformer : public AttributeStreamNode { + // node applies a transformation on specified attributes +public: + SelectiveAttributeTransformer(const char *attributeName, bool (*)(const char *, + uint32 , off_t , void *, void *), void *params); + virtual ~SelectiveAttributeTransformer(); + + virtual off_t Read(const char *name, const char *foreignName, uint32 type, off_t size, + void *buffer, void (*swapFunc)(void *) = 0); + + virtual void Rewind(); + +protected: + virtual bool WillTransform(const char *name, uint32 type, off_t size, const char *data) const; + // override to implement filtering; should only return true if transformation will + // occur + virtual char *CopyAndApplyTransformer(const char *name, uint32 type, off_t size, const char *data); + // makes a copy of data + virtual bool ApplyTransformer(const char *name, uint32 type, off_t size, char *data); + // transforms in place + virtual const AttributeInfo *Next(); + virtual const char *Get(); + +private: + AttributeInfo fCurrentAttr; + const char *fAttributeNameToTransform; + bool (*fTransformFunc)(const char *, uint32 , off_t , void *, void *); + void *fTransformParams; + + BObjectList fTransformedBuffers; + + typedef AttributeStreamNode _inherited; +}; + +template +class AttributeStreamConstValue : public AttributeStreamNode { +public: + AttributeStreamConstValue(const char *name, uint32 attributeType, Type value); +protected: + + virtual bool CanFeed() const { return true; } + virtual void Rewind() { fRewound = true; } + virtual const AttributeInfo *Next(); + virtual const char *Get(); + virtual bool Fill(char *buffer) const; + + int32 Find(const char *name, uint32 type) const; + +private: + AttributeInfo fAttr; + Type fValue; + bool fRewound; + + typedef AttributeStreamNode _inherited; +}; + +template +AttributeStreamConstValue::AttributeStreamConstValue(const char *name, + uint32 attributeType, Type value) + : fAttr(name, attributeType, sizeof(Type)), + fValue(value), + fRewound(true) +{ +} + +template +const AttributeInfo * +AttributeStreamConstValue::Next() +{ + if (!fRewound) + return NULL; + + fRewound = false; + return &fAttr; +} + +template +const char * +AttributeStreamConstValue::Get() +{ + return (const char *)&fValue; +} + +template +bool +AttributeStreamConstValue::Fill(char *buffer) const +{ + memcpy(buffer, &fValue, sizeof(Type)); + return true; +} + +template +int32 +AttributeStreamConstValue::Find(const char *name, uint32 type) const +{ + if (strcmp(fAttr.Name(), name) == 0 && type = fAttr.Type()) + return 0; + + return -1; +} + +class AttributeStreamBoolValue : public AttributeStreamConstValue { +public: + AttributeStreamBoolValue(const char *name, bool value) + : AttributeStreamConstValue(name, B_BOOL_TYPE, value) + {} +}; + +class AttributeStreamInt32Value : public AttributeStreamConstValue { +public: + AttributeStreamInt32Value(const char *name, int32 value) + : AttributeStreamConstValue(name, B_INT32_TYPE, value) + {} +}; + +class AttributeStreamInt64Value : public AttributeStreamConstValue { +public: + AttributeStreamInt64Value(const char *name, int64 value) + : AttributeStreamConstValue(name, B_INT64_TYPE, value) + {} +}; + +class AttributeStreamRectValue : public AttributeStreamConstValue { +public: + AttributeStreamRectValue(const char *name, BRect value) + : AttributeStreamConstValue(name, B_RECT_TYPE, value) + {} +}; + +class AttributeStreamFloatValue : public AttributeStreamConstValue { +public: + AttributeStreamFloatValue(const char *name, float value) + : AttributeStreamConstValue(name, B_FLOAT_TYPE, value) + {} +}; + +} + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/Attributes.h b/src/kits/tracker/Attributes.h new file mode 100644 index 0000000000..6125f5434d --- /dev/null +++ b/src/kits/tracker/Attributes.h @@ -0,0 +1,173 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef _ATTRIBUTES_H +#define _ATTRIBUTES_H + +namespace BPrivate { + +// viewable attributes +#define kAttrStatName "_stat/name" +#define kAttrStatSize "_stat/size" +#define kAttrStatModified "_stat/modified" +#define kAttrStatCreated "_stat/created" +#define kAttrStatMode "_stat/mode" +#define kAttrStatOwner "_stat/owner" +#define kAttrStatGroup "_stat/group" +#define kAttrPath "_trk/path" +#define kAttrOriginalPath "_trk/original_path" +#define kAttrAppVersion "_trk/app_version" +#define kAttrSystemVersion "_trk/system_version" +#define kAttrOpenWithRelation "_trk/open_with_relation" + +// private attributes +#define kAttrWindowFrame "_trk/windframe" +#define kAttrWindowWorkspace "_trk/windwkspc" + +#define kAttrQueryString "_trk/qrystr" +#define kAttrQueryVolume "_trk/qryvol1" + +#define kAttrMIMEType "BEOS:TYPE" +#define kAttrAppSignature "BEOS:APP_SIG" +#define kAttrPreferredApp "BEOS:PREF_APP" +#define kAttrLargeIcon "BEOS:L:STD_ICON" +#define kAttrMiniIcon "BEOS:M:STD_ICON" + +#define kAttrDisksFrame "_trk/d_windframe" +#define kAttrDisksWorkspace "_trk/d_windwkspc" + +#define kAttrOpenWindows "_trk/_windows_to_open_" + +#define kAttrClippingFile "_trk/_clipping_file_" + + +#define kAttrQueryInitialMode "_trk/qryinitmode" +#define kAttrQueryInitialString "_trk/qryinitstr" +#define kAttrQueryInitialNumAttrs "_trk/qryinitnumattrs" +#define kAttrQueryInitialAttrs "_trk/qryinitattrs" +#define kAttrQueryInitialMime "_trk/qryinitmime" +#define kAttrQueryLastChange "_trk/qrylastchange" + + +#define kAttrQueryMoreOptions_le "_trk/qrymoreoptions_le" +#define kAttrQueryMoreOptions_be "_trk/qrymoreoptions" + +#define kAttrQueryTemplate "_trk/queryTemplate" +#define kAttrQueryTemplateName "_trk/queryTemplateName" +#define kAttrDynamicDateQuery "_trk/queryDynamicDate" +// attributes that need endian swapping (stored as raw) + +#define kAttrPoseInfo_be "_trk/pinfo" +#define kAttrPoseInfo_le "_trk/pinfo_le" +#define kAttrDisksPoseInfo_be "_trk/d_pinfo" +#define kAttrDisksPoseInfo_le "_trk/d_pinfo_le" +#define kAttrColumns_be "_trk/columns" +#define kAttrColumns_le "_trk/columns_le" +#define kAttrViewState_be "_trk/viewstate" +#define kAttrViewState_le "_trk/viewstate_le" +#define kAttrDisksViewState_be "_trk/d_viewstate" +#define kAttrDisksViewState_le "_trk/d_viewstate_le" +#define kAttrDisksColumns_be "_trk/d_columns" +#define kAttrDisksColumns_le "_trk/d_columns_le" + +#define kAttrExtendedPoseInfo_be "_trk/xtpinfo" +#define kAttrExtendedPoseInfo_le "_trk/xtpinfo_le" +#define kAttrExtendedDisksPoseInfo_be "_trk/xt_d_pinfo" +#define kAttrExtendedDisksPoseInfo_le "_trk/xt_d_pinfo_le" + +#if B_HOST_IS_LENDIAN +#define kEndianSuffix "_le" +#define kForeignEndianSuffix "" + +#define kAttrDisksPoseInfo kAttrDisksPoseInfo_le +#define kAttrDisksPoseInfoForeign kAttrDisksPoseInfo_be + +#define kAttrPoseInfo kAttrPoseInfo_le +#define kAttrPoseInfoForeign kAttrPoseInfo_be + +#define kAttrColumns kAttrColumns_le +#define kAttrColumnsForeign kAttrColumns_be + +#define kAttrViewState kAttrViewState_le +#define kAttrViewStateForeign kAttrViewState_be + +#define kAttrDisksViewState kAttrDisksViewState_le +#define kAttrDisksViewStateForeign kAttrDisksViewState_be + +#define kAttrDisksColumns kAttrDisksColumns_le +#define kAttrDisksColumnsForeign kAttrDisksColumns_be + +#define kAttrQueryMoreOptions kAttrQueryMoreOptions_le +#define kAttrQueryMoreOptionsForeign kAttrQueryMoreOptions_be +#define kAttrExtendedPoseInfo kAttrExtendedPoseInfo_le +#define kAttrExtendedPoseInfoForegin kAttrExtendedPoseInfo_be +#define kAttrExtendedDisksPoseInfo kAttrExtendedDisksPoseInfo_le +#define kAttrExtendedDisksPoseInfoForegin kAttrExtendedDisksPoseInfo_be + +#else +#define kEndianSuffix "" +#define kForeignEndianSuffix "_le" + +#define kAttrDisksPoseInfo kAttrDisksPoseInfo_be +#define kAttrDisksPoseInfoForeign kAttrDisksPoseInfo_le + +#define kAttrPoseInfo kAttrPoseInfo_be +#define kAttrPoseInfoForeign kAttrPoseInfo_le + +#define kAttrColumns kAttrColumns_be +#define kAttrColumnsForeign kAttrColumns_le + +#define kAttrViewState kAttrViewState_be +#define kAttrViewStateForeign kAttrViewState_le + +#define kAttrDisksViewState kAttrDisksViewState_be +#define kAttrDisksViewStateForeign kAttrDisksViewState_le + +#define kAttrDisksColumns kAttrDisksColumns_be +#define kAttrDisksColumnsForeign kAttrDisksColumns_le + +#define kAttrQueryMoreOptions kAttrQueryMoreOptions_be +#define kAttrQueryMoreOptionsForeign kAttrQueryMoreOptions_le +#define kAttrExtendedPoseInfo kAttrExtendedPoseInfo_be +#define kAttrExtendedPoseInfoForegin kAttrExtendedPoseInfo_le +#define kAttrExtendedDisksPoseInfo kAttrExtendedDisksPoseInfo_be +#define kAttrExtendedDisksPoseInfoForegin kAttrExtendedDisksPoseInfo_le + +#endif + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/AutoMounter.cpp b/src/kits/tracker/AutoMounter.cpp new file mode 100644 index 0000000000..4b635a4d60 --- /dev/null +++ b/src/kits/tracker/AutoMounter.cpp @@ -0,0 +1,1270 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include "AutoMounter.h" + +#include "AutoLock.h" +#include "AutoMounterSettings.h" +#include "Commands.h" +#include "FSUtils.h" +#include "Tracker.h" +#include "TrackerSettings.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + + +static const uint32 kStartPolling = 'strp'; +#if _INCLUDES_CLASS_DEVICE_MAP +static const char *kAutoMounterSettings = "automounter_settings"; +#endif + +struct OneMountFloppyParams { + status_t result; +}; + +#if _INCLUDES_CLASS_DEVICE_MAP +static bool gSilentAutoMounter; +#endif +static BMessage gSettingsMessage; + + +#if xDEBUG +static Partition * +DumpPartition(Partition *_DEVICE_MAP_ONLY(partition), void*) +{ +#if _INCLUDES_CLASS_DEVICE_MAP + partition->Dump(); +#endif + return 0; +} +#endif + + +#if _INCLUDES_CLASS_DEVICE_MAP + + +struct MountPartitionParams { + int32 uniqueID; + status_t result; +}; + + +/** Sets the Tracker Shell's AutoMounter to monitor a node. + * n.b. Get's the one AutoMounter and uses Tracker's _special_ WatchNode. + * + * @param nodeToWatch (node_ref const * const) The Node to monitor. + * @param flags (uint32) watch_node flags from NodeMonitor. + * @return (status_t) watch_node status or B_BAD_TYPE if not a TTracker app. + */ + +static status_t +AutoMounterWatchNode(const node_ref *nodeRef, uint32 flags) +{ + ASSERT(nodeToWatch != NULL); + + TTracker *tracker = dynamic_cast(be_app); + if (tracker != NULL) + return TTracker::WatchNode(nodeRef, flags, BMessenger(0, tracker->AutoMounterLoop())); + + return B_BAD_TYPE; +} + + +/** Tries to mount the partition and if it can it watches mount point. + * + * @param partition (Partition * const) The partition to mount. + */ + +static status_t +MountAndWatch(Partition *partition) +{ + ASSERT(partition != NULL); + + status_t status = partition->Mount(); + if (status != B_OK) + return status; + + // Start watching this mount point + node_ref nodeToWatch; + status = partition->GetMountPointNodeRef(&nodeToWatch); + if (status != B_OK) { + PRINT(("Couldn't get mount point node ref: %s\n", strerror(result))); + return status; + } + + return AutoMounterWatchNode(&nodeToWatch, B_WATCH_NAME); +} + + +static Partition * +TryMountingEveryOne(Partition *partition, void *castToParams) +{ + MountPartitionParams *params = (MountPartitionParams *)castToParams; + + if (partition->Mounted() == kMounted) { + if (!gSilentAutoMounter) + PRINT(("%s already mounted\n", partition->VolumeName())); + } else { + status_t result = MountAndWatch(partition); + // return error if caller asked for it + if (params) + params->result = result; + + if (!gSilentAutoMounter) { + if (result == B_OK) + PRINT(("%s mounted OK\n", partition->VolumeName())); + else + PRINT(("Error '%s' mounting %s\n", + strerror(result), partition->VolumeName())); + } + + if (params && result != B_OK) + // signal an error + return partition; + } + return NULL; +} + + +static Partition * +OneTryMountingFloppy(Partition *partition, void *castToParams) +{ + OneMountFloppyParams *params = (OneMountFloppyParams *)castToParams; + if (partition->GetDevice()->IsFloppy()){ + + status_t result = MountAndWatch(partition); + // return error if caller asked for it + if (params) + params->result = result; + + return partition; + } + return 0; +} + + +static Partition * +OneMatchFloppy(Partition *partition, void *) +{ + if (partition->GetDevice()->IsFloppy()) + return partition; + + return 0; +} + + +static Partition * +TryMountingBFSOne(Partition *partition, void *params) +{ + if (strcmp(partition->FileSystemShortName(), "bfs") == 0) + return TryMountingEveryOne(partition, params); + + return NULL; +} + + +static Partition * +TryMountingRestoreOne(Partition *partition, void *params) +{ + Session *session = partition->GetSession(); + Device *device = session->GetDevice(); + + // create the name for the virtual device + char path[B_FILE_NAME_LENGTH]; + int len = (int)strlen(device->Name()) - (int)strlen("/raw"); + if (session->CountPartitions() != 1) + sprintf(path, "%.*s/%ld_%ld", len, device->Name(), session->Index(), partition->Index()); + else + sprintf(path, "%s", device->Name()); + + // Find the name of the current device/volume in the saved settings + // and open if found. + const char *volumename; + if (gSettingsMessage.FindString(path, &volumename) == B_OK + && strcmp(volumename, partition->VolumeName()) == 0) + return TryMountingEveryOne(partition, params); + + return NULL; +} + + +static Partition * +TryMountingHFSOne(Partition *partition, void *params) +{ + if (strcmp(partition->FileSystemShortName(), "hfs") == 0) + return TryMountingEveryOne(partition, params); + + return NULL; +} + + +struct FindPartitionByDeviceIDParams { + dev_t dev; +}; + +static Partition * +FindPartitionByDeviceID(Partition *partition, void *castToParams) +{ + FindPartitionByDeviceIDParams *params = (FindPartitionByDeviceIDParams*) castToParams; + if (params->dev == partition->VolumeDeviceID()) + return partition; + + return 0; +} + + +static Partition * +TryWatchMountPoint(Partition *partition, void *) +{ + node_ref nodeRef; + if (partition->GetMountPointNodeRef(&nodeRef) == B_OK) + AutoMounterWatchNode(&nodeRef, B_WATCH_NAME); + + return 0; +} + + +static Partition * +TryMountVolumeByID(Partition *partition, void *params) +{ + PRINT(("Try mounting partition %i\n", partition->UniqueID())); + if (!partition->Hidden() && partition->UniqueID() == + ((MountPartitionParams *)params)->uniqueID) { + Partition *result = TryMountingEveryOne(partition, params); + if (result) + return result; + + return partition; + } + return NULL; +} + + +static Partition * +AutomountOne(Partition *partition, void *castToParams) +{ + PRINT(("Partition %s not mounted\n", partition->Name())); + AutomountParams *params = (AutomountParams *)castToParams; + + if (params->mountRemovableDisksOnly + && (!partition->GetDevice()->NoMedia() + && !partition->GetDevice()->Removable())) + // not removable, don't mount it + return NULL; + + if (params->mountAllFS) + return TryMountingEveryOne(partition, NULL); + if (params->mountBFS) + return TryMountingBFSOne(partition, NULL); + if (params->mountHFS) + return TryMountingHFSOne(partition, NULL); + + return NULL; +} + + +static Partition * +NotifyFloppyNotMountable(Partition *partition, void *) +{ + if (partition->Mounted() != kMounted + && partition->GetDevice()->IsFloppy() + && !partition->Hidden()) { + (new BAlert("", "The format of the floppy disk in the disk drive is " + "not recognized or the disk has never been formatted.", "OK"))->Go(0); + partition->GetDevice()->Eject(); + } + return NULL; +} + + +#endif // #if _INCLUDES_CLASS_DEVICE_MAP + + +#ifdef MOUNT_MENU_IN_DESKBAR + +// just for testing + +Partition * +AddMountableItemToMessage(Partition *partition, void *castToParams) +{ + BMessage *message = static_cast(castToParams); + + message->AddString("DeviceName", partition->GetDevice()->Name()); + const char *name; + if (*partition->VolumeName()) + name = partition->VolumeName(); + else if (*partition->Type()) + name = partition->Type(); + else + name = partition->GetDevice()->DisplayName(); + + message->AddString("DisplayName", name); + BMessage invokeMsg; + if (partition->GetDevice()->IsFloppy()) + invokeMsg.what = kTryMountingFloppy; + else + invokeMsg.what = kMountVolume; + invokeMsg.AddInt32("id", partition->UniqueID()); + message->AddMessage("InvokeMessage", &invokeMsg); + return NULL; +} + +#endif // #ifdef MOUNT_MENU_IN_DESKBAR + + +AutoMounter::AutoMounter(bool _DEVICE_MAP_ONLY(checkRemovableOnly), + bool _DEVICE_MAP_ONLY(checkCDs), bool _DEVICE_MAP_ONLY(checkFloppies), + bool _DEVICE_MAP_ONLY(checkOtherRemovable), bool _DEVICE_MAP_ONLY(autoMountRemovablesOnly), + bool _DEVICE_MAP_ONLY(autoMountAll), bool _DEVICE_MAP_ONLY(autoMountAllBFS), + bool _DEVICE_MAP_ONLY(autoMountAllHFS), + bool initialMountAll, bool initialMountAllBFS, bool initialMountRestore, + bool initialMountAllHFS) + : BLooper("DirPoller", B_LOW_PRIORITY), + fInitialMountAll(initialMountAll), + fInitialMountAllBFS(initialMountAllBFS), + fInitialMountRestore(initialMountRestore), + fInitialMountAllHFS(initialMountAllHFS), + fSuspended(false), + fQuitting(false) +{ +#if _INCLUDES_CLASS_DEVICE_MAP + fScanParams.shortestRescanHartbeat = 5000000; + fScanParams.checkFloppies = checkFloppies; + fScanParams.checkCDROMs = checkCDs; + fScanParams.checkOtherRemovable = checkOtherRemovable; + fScanParams.removableOrUnknownOnly = checkRemovableOnly; + + fAutomountParams.mountAllFS = autoMountAll; + fAutomountParams.mountBFS = autoMountAllBFS; + fAutomountParams.mountHFS = autoMountAllHFS; + fAutomountParams.mountRemovableDisksOnly = autoMountRemovablesOnly; + + gSilentAutoMounter = true; + + if (!BootedInSafeMode()) { + ReadSettings(); + thread_id rescan = spawn_thread(AutoMounter::InitialRescanBinder, + "AutomountInitialScan", B_DISPLAY_PRIORITY, this); + resume_thread(rescan); + } else { + // defeat automounter in safe mode, don't even care about the settings + fAutomountParams.mountAllFS = false; + fAutomountParams.mountBFS = false; + fAutomountParams.mountHFS = false; + fInitialMountAll = false; + fInitialMountAllBFS = false; + fInitialMountRestore = false; + fInitialMountAllHFS = false; + } + + // Watch mount/unmount + TTracker::WatchNode(0, B_WATCH_MOUNT, this); +#endif +} + + +AutoMounter::~AutoMounter() +{ +} + + +Partition* AutoMounter::FindPartition(dev_t _DEVICE_MAP_ONLY(dev)) +{ +#if _INCLUDES_CLASS_DEVICE_MAP + FindPartitionByDeviceIDParams params; + params.dev = dev; + return fList.EachMountedPartition(FindPartitionByDeviceID, ¶ms); +#else + return NULL; +#endif +} + + +void +AutoMounter::RescanDevices() +{ +#if _INCLUDES_CLASS_DEVICE_MAP + stop_watching(this); + fList.RescanDevices(true); + fList.UpdateMountingInfo(); + fList.EachMountedPartition(TryWatchMountPoint, 0); + TTracker::WatchNode(0, B_WATCH_MOUNT, this); + fList.EachMountedPartition(TryWatchMountPoint, 0); +#endif +} + + +void +AutoMounter::MessageReceived(BMessage *message) +{ + switch (message->what) { +#if _INCLUDES_CLASS_DEVICE_MAP + case kAutomounterRescan: + RescanDevices(); + break; + + case kStartPolling: + // PRINT(("starting the automounter\n")); + + fScanThread = spawn_thread(AutoMounter::WatchVolumeBinder, +#if DEBUG + "HiroshiLikesAtomountScan", // long story +#else + "AutomountScan", +#endif + B_LOW_PRIORITY, this); + resume_thread(fScanThread); + break; + + case kMountVolume: + MountVolume(message); + break; + + case kUnmountVolume: + UnmountAndEjectVolume(message); + break; + + case kSetAutomounterParams: + { + bool rescanNow = false; + message->FindBool("rescanNow", &rescanNow); + SetParams(message, rescanNow); + WriteSettings(); + break; + } + + case kMountAllNow: + RescanDevices(); + MountAllNow(); + break; + + case kSuspendAutomounter: + SuspendResume(true); + break; + + case kResumeAutomounter: + SuspendResume(false); + break; + + case kTryMountingFloppy: + TryMountingFloppy(); + break; + + case B_NODE_MONITOR: + { + int32 opcode; + if (message->FindInt32("opcode", &opcode) != B_OK) + break; + + switch (opcode) { + case B_DEVICE_MOUNTED: { + WRITELOG(("** Received Device Mounted Notification")); + dev_t device; + if (message->FindInt32("new device", &device) == B_OK) { + Partition *partition = FindPartition(device); + if (partition == NULL || partition->Mounted() != kMounted) { + WRITELOG(("Device %i not in device list. Someone mounted it outside " + "of Tracker", device)); + + // + // This is the worst case. Someone has mounted + // something from outside of tracker. + // Unfortunately, there's no easy way to tell which + // partition was just mounted (or if we even know about the device), + // so stop watching all nodes, rescan to see what is now mounted, + // and start watching again. + // + RescanDevices(); + } else + WRITELOG(("Found partition\n")); + } else { + WRITELOG(("ERROR: Could not find mounted device ID in message")); + PRINT_OBJECT(*message); + } + + break; + } + + + case B_DEVICE_UNMOUNTED: { + WRITELOG(("*** Received Device Unmounted Notification")); + dev_t device; + if (message->FindInt32("device", &device) == B_OK) { + Partition *partition = FindPartition(device); + + if (partition != 0) { + WRITELOG(("Found device in device list. Updating state to unmounted.")); + partition->SetMountState(kNotMounted); + } else + WRITELOG(("Unmounted device %i was not in device list", device)); + } else { + WRITELOG(("ERROR: Could not find unmounted device ID in message")); + PRINT_OBJECT(*message); + } + + break; + } + + + // The name of a mount point has changed + case B_ENTRY_MOVED: { + WRITELOG(("*** Received Mount Point Renamed Notification")); + + const char *newName; + if (message->FindString("name", &newName) != B_OK) { + WRITELOG(("ERROR: Couldn't find name field in update message")); + PRINT_OBJECT(*message); + break ; + } + + // + // When the node monitor reports a move, it gives the + // parent device and inode that moved. The problem is + // that the inode is the inode of root *in* the filesystem, + // which is generally always the same number for every + // filesystem of a type. + // + // What we'd really like is the device that the moved + // volume is mounted on. Find this by using the + // *new* name and directory, and then stat()ing that to + // find the device. + // + dev_t parentDevice; + if (message->FindInt32("device", &parentDevice) != B_OK) { + WRITELOG(("ERROR: Couldn't find 'device' field in update" + " message")); + PRINT_OBJECT(*message); + break; + } + + ino_t toDirectory; + if (message->FindInt64("to directory", &toDirectory)!=B_OK){ + WRITELOG(("ERROR: Couldn't find 'to directory' field in update" + "message")); + PRINT_OBJECT(*message); + break; + } + + entry_ref root_entry(parentDevice, toDirectory, newName); + + BNode entryNode(&root_entry); + if (entryNode.InitCheck() != B_OK) { + WRITELOG(("ERROR: Couldn't create mount point entry node: %s/n", + strerror(entryNode.InitCheck()))); + break; + } + + node_ref mountPointNode; + if (entryNode.GetNodeRef(&mountPointNode) != B_OK) { + WRITELOG(("ERROR: Couldn't get node ref for new mount point")); + break; + } + + + WRITELOG(("Attempt to rename device %li to %s", mountPointNode.device, + newName)); + + Partition *partition = FindPartition(mountPointNode.device); + if (partition != NULL) { + WRITELOG(("Found device, changing name.")); + + BVolume mountVolume(partition->VolumeDeviceID()); + BDirectory mountDir; + mountVolume.GetRootDirectory(&mountDir); + BPath dirPath(&mountDir, 0); + + partition->SetMountedAt(dirPath.Path()); + partition->SetVolumeName(newName); + break; + } + else + WRITELOG(("ERROR: Device %li does not appear to be present", + mountPointNode.device)); + } + } + } + break; + + +#endif + default: + BLooper::MessageReceived(message); + break; + } +} + + +status_t +AutoMounter::WatchVolumeBinder(void *_DEVICE_MAP_ONLY(castToThis)) +{ +#if _INCLUDES_CLASS_DEVICE_MAP + static_cast(castToThis)->WatchVolumes(); + return B_OK; +#else + return B_UNSUPPORTED; +#endif +} + + +void +AutoMounter::WatchVolumes() +{ +#if _INCLUDES_CLASS_DEVICE_MAP + for(;;) { + snooze(fScanParams.shortestRescanHartbeat); + + AutoLock lock(this); + if (!lock) + break; + + if (fQuitting) { + lock.Unlock(); + break; + } + + if (!fSuspended && fList.CheckDevicesChanged(&fScanParams)) { + fList.UnmountDisappearedPartitions(); + fList.UpdateChangedDevices(&fScanParams); + fList.EachMountablePartition(AutomountOne, &fAutomountParams); + } + } +#endif +} + + +static Device * +FindFloppyDevice(Device *_DEVICE_MAP_ONLY(device), void *) +{ +#if _INCLUDES_CLASS_DEVICE_MAP + if (device->IsFloppy()) + return device; +#endif + + return 0; +} + + +void +AutoMounter::EachMountableItemAndFloppy(EachPartitionFunction _DEVICE_MAP_ONLY(func), + void *_DEVICE_MAP_ONLY(passThru)) +{ +#if _INCLUDES_CLASS_DEVICE_MAP + AutoLock lock(this); + +#if 0 + // + // Rescan now to see if anything has changed. + // + if (fList.CheckDevicesChanged(&fScanParams)) { + fList.UpdateChangedDevices(&fScanParams); + fList.UnmountDisappearedPartitions(); + } +#endif + + // + // If the floppy has never been mounted, it won't have a partition + // in the device list (but it will have a device entry). If it has, the + // partition will appear, but will be set not mounted. Code here works + // around this. + // + if (!IsFloppyMounted() && !FloppyInList()) { + Device *floppyDevice = fList.EachDevice(FindFloppyDevice, 0); + + // + // See comments under 'EachPartition' + // + if (floppyDevice != 0) { + Session session(floppyDevice, "floppy", 0, 0, 0); + Partition partition(&session, "floppy", "unknown", + "unknown", "unknown", "floppy", "", 0, 0, 0, false); + + (func)(&partition, passThru); + } + } + + fList.EachMountablePartition(func, passThru); +#endif +} + + +void +AutoMounter::EachMountedItem(EachPartitionFunction _DEVICE_MAP_ONLY(func), + void *_DEVICE_MAP_ONLY(passThru)) +{ +#if _INCLUDES_CLASS_DEVICE_MAP + AutoLock lock(this); + fList.EachMountedPartition(func, passThru); +#endif +} + + +Partition * +AutoMounter::EachPartition(EachPartitionFunction _DEVICE_MAP_ONLY(func), + void *_DEVICE_MAP_ONLY(passThru)) +{ +#if _INCLUDES_CLASS_DEVICE_MAP + AutoLock lock(this); + + if (!IsFloppyMounted() && !FloppyInList()) { + Device *floppyDevice = fList.EachDevice(FindFloppyDevice, 0); + + // + // Add a floppy to the list. It normally doesn't appear + // there when a floppy hasn't been mounted because it + // doesn't have any partitions. Note that this makes sure + // that a floppy device exists before adding it, because + // some systems don't have floppy drives (unbelievable, but + // true). + // + if (floppyDevice != 0) { + Session session(floppyDevice, "floppy", 0, 0, 0); + Partition partition(&session, "floppy", "unknown", + "unknown", "unknown", "floppy", "", 0, 0, 0, false); + + Partition *result = func(&partition, passThru); + if (result != NULL) + return result; + } + } + + return fList.EachPartition(func, passThru); +#else + return NULL; +#endif +} + + +void +AutoMounter::CheckVolumesNow() +{ +#if _INCLUDES_CLASS_DEVICE_MAP + AutoLock lock(this); + if (fList.CheckDevicesChanged(&fScanParams)) { + fList.UnmountDisappearedPartitions(); + fList.UpdateChangedDevices(&fScanParams); + if (!fSuspended) + fList.EachMountablePartition(AutomountOne, &fAutomountParams); + } +#endif +} + + +void +AutoMounter::SuspendResume(bool _DEVICE_MAP_ONLY(suspend)) +{ +#if _INCLUDES_CLASS_DEVICE_MAP + AutoLock lock(this); + fSuspended = suspend; + if (fSuspended) + suspend_thread(fScanThread); + else + resume_thread(fScanThread); +#endif +} + + +void +AutoMounter::MountAllNow() +{ +#if _INCLUDES_CLASS_DEVICE_MAP + AutoLock lock(this); + + DeviceScanParams mountAllParams; + mountAllParams.checkFloppies = true; + mountAllParams.checkCDROMs = true; + mountAllParams.checkOtherRemovable = true; + mountAllParams.removableOrUnknownOnly = true; + + fList.UnmountDisappearedPartitions(); + fList.UpdateChangedDevices(&mountAllParams); + fList.EachMountablePartition(TryMountingEveryOne, 0); + fList.EachPartition(NotifyFloppyNotMountable, 0); +#endif +} + + +void +AutoMounter::TryMountingFloppy() +{ +#if _INCLUDES_CLASS_DEVICE_MAP + AutoLock lock(this); + + DeviceScanParams mountAllParams; + mountAllParams.checkFloppies = true; + mountAllParams.checkCDROMs = false; + mountAllParams.checkOtherRemovable = false; + mountAllParams.removableOrUnknownOnly = false; + + fList.UnmountDisappearedPartitions(); + fList.UpdateChangedDevices(&mountAllParams); + OneMountFloppyParams params; + params.result = B_ERROR; + fList.EachMountablePartition(OneTryMountingFloppy, ¶ms); + if (params.result != B_OK) + (new BAlert("", "The format of the floppy disk in the disk drive is " + "not recognized or the disk has never been formatted.", "OK")) + ->Go(0); +#endif +} + + +bool +AutoMounter::IsFloppyMounted() +{ +#if _INCLUDES_CLASS_DEVICE_MAP + AutoLock lock(this); + return fList.EachMountedPartition(OneMatchFloppy, 0) != NULL; +#else + return false; +#endif +} + + +bool +AutoMounter::FloppyInList() +{ +#if _INCLUDES_CLASS_DEVICE_MAP + AutoLock lock(this); + return fList.EachPartition(OneMatchFloppy, 0) != NULL; +#else + return false; +#endif +} + + +void +AutoMounter::MountVolume(BMessage *_DEVICE_MAP_ONLY(message)) +{ +#if _INCLUDES_CLASS_DEVICE_MAP + int32 uniqueID; + if (message->FindInt32("id", &uniqueID) == B_OK) { + + if (uniqueID == kFloppyID) { + TryMountingFloppy(); + return; + } + + MountPartitionParams params; + params.uniqueID = uniqueID; + params.result = B_OK; + + if (EachPartition(TryMountVolumeByID, ¶ms) == NULL) + (new BAlert("", "The format of this volume is unrecognized, or it has " + "never been formatted", "OK"))->Go(0); + else if (params.result != B_OK) { + BString string; + string << "Error mounting volume. (" << strerror(params.result) << ")"; + (new BAlert("", string.String(), "OK"))->Go(0); + } + } +#endif +} + + +status_t +AutoMounter::InitialRescanBinder(void *_DEVICE_MAP_ONLY(castToThis)) +{ +#if _INCLUDES_CLASS_DEVICE_MAP + // maybe this can help the strange Tracker lock-up at startup + snooze(500000LL); // wait half a second + + AutoMounter *self = static_cast(castToThis); + self->InitialRescan(); + + // Start watching nodes that were mounted before tracker started + (self->fList).EachMountedPartition(TryWatchMountPoint, 0); + + self->PostMessage(kStartPolling, 0); +#endif + return B_OK; +} + + +void +AutoMounter::InitialRescan() +{ +#if _INCLUDES_CLASS_DEVICE_MAP + AutoLock lock(this); + + fList.RescanDevices(false); + fList.UpdateMountingInfo(); + + // if called after spawn_thread, must lock fList + if (fInitialMountAll) { +//+ PRINT(("mounting all volumes\n")); + fList.EachMountablePartition(TryMountingEveryOne, NULL); + } + + if (fInitialMountAllHFS) { +//+ PRINT(("mounting all hfs volumes\n")); + fList.EachMountablePartition(TryMountingHFSOne, NULL); + } + + if (fInitialMountAllBFS) { +//+ PRINT(("mounting all bfs volumes\n")); + fList.EachMountablePartition(TryMountingBFSOne, NULL); + } + + if (fInitialMountRestore) { +//+ PRINT(("restoring all volumes\n")); + fList.EachMountablePartition(TryMountingRestoreOne, NULL); + } +#endif +} + + +struct UnmountDeviceParams { + dev_t device; + status_t result; +}; + +static Partition * +UnmountIfMatchingID(Partition *_DEVICE_MAP_ONLY(partition), + void *_DEVICE_MAP_ONLY(castToParams)) +{ +#if _INCLUDES_CLASS_DEVICE_MAP + UnmountDeviceParams *params = (UnmountDeviceParams *)castToParams; + + if (partition->VolumeDeviceID() == params->device) { + + TTracker *tracker = dynamic_cast(be_app); + if (tracker && tracker->QueryActiveForDevice(params->device)) { + BString text; + text << "To unmount " << partition->VolumeName() << " some query " + "windows have to be closed. Would you like to close the query " + "windows?"; + if ((new BAlert("", text.String(), "Cancel", "Close and unmount", NULL, + B_WIDTH_FROM_LABEL))->Go() == 0) + return partition; + tracker->CloseActiveQueryWindows(params->device); + } + + params->result = partition->Unmount(); + Device *device = partition->GetDevice(); + bool deviceHasMountedPartitions = false; + + if (params->result == B_OK && device->Removable()) { + for (int32 sessionIndex = 0; ; sessionIndex++) { + Session *session = device->SessionAt(sessionIndex); + if (!session) + break; + + for (int32 partitionIndex = 0; ; partitionIndex++) { + Partition *partition = session->PartitionAt(partitionIndex); + if (!partition) + break; + + if (partition->Mounted() == kMounted) { + deviceHasMountedPartitions = true; + break; + } + } + } + + if (!deviceHasMountedPartitions + && TrackerSettings().EjectWhenUnmounting()) + params->result = partition->GetDevice()->Eject(); + } + + return partition; + } + +#endif + return NULL; +} + + +void +AutoMounter::UnmountAndEjectVolume(BMessage *_DEVICE_MAP_ONLY(message)) +{ +#if _INCLUDES_CLASS_DEVICE_MAP + dev_t device; + if (message->FindInt32("device_id", &device) != B_OK) + return; + + PRINT(("Unmount device %i\n", device)); + + AutoLock lock(this); + + UnmountDeviceParams params; + params.device = device; + params.result = B_OK; + Partition *partition = fList.EachMountedPartition(UnmountIfMatchingID, + ¶ms); + + if (!partition) { + PRINT(("Couldn't unmount partition. Rescan and try again\n")); + + // could not find partition - must have been mounted by someone + // else + // sync up and try again + // this should really be handled by watching for mount and unmount + // events like the tracker does, not doing that because it is + // a bigger change and we are close to freezing + fList.UnmountDisappearedPartitions(); + + DeviceScanParams syncRescanParams; + syncRescanParams.checkFloppies = true; + syncRescanParams.checkCDROMs = true; + syncRescanParams.checkOtherRemovable = true; + syncRescanParams.removableOrUnknownOnly = true; + + fList.UpdateChangedDevices(&syncRescanParams); + partition = fList.EachMountedPartition(UnmountIfMatchingID, ¶ms); + } + + if (!partition) { + PRINT(("Device not in list, unmounting directly\n")); + + char path[B_FILE_NAME_LENGTH]; + + BVolume vol(device); + status_t err = vol.InitCheck(); + if (err == B_OK) { + BDirectory mountPoint; + if (err == B_OK) + err = vol.GetRootDirectory(&mountPoint); + + BPath mountPointPath; + if (err == B_OK) + err = mountPointPath.SetTo(&mountPoint, "."); + + if (err == B_OK) + strcpy(path, mountPointPath.Path()); + } + + if (err == B_OK) { + PRINT(("unmounting '%s'\n", path)); + err = unmount(path); + } + + if (err == B_OK) { + PRINT(("deleting '%s'\n", path)); + err = rmdir(path); + } + + if (err != B_OK) { + + PRINT(("error %s\n", strerror(err))); + BString text; + text << "Could not unmount disk"; + (new BAlert("", text.String(), "OK", NULL, NULL, B_WIDTH_AS_USUAL, + B_WARNING_ALERT))->Go(0); + } + + } else if (params.result != B_OK) { + BString text; + text << "Could not unmount disk " << partition->VolumeName() << + ". An item on the disk is busy."; + (new BAlert("", text.String(), "OK", NULL, NULL, B_WIDTH_AS_USUAL, + B_WARNING_ALERT))->Go(0); + } +#endif +} + + +bool +AutoMounter::QuitRequested() +{ +#if _INCLUDES_CLASS_DEVICE_MAP + if (!BootedInSafeMode()) + // don't write out settings in safe mode - this would overwrite the + // normal, non-safe mode settings + WriteSettings(); + +#endif + return true; +} + + +void +AutoMounter::ReadSettings() +{ +#if _INCLUDES_CLASS_DEVICE_MAP + BPath directoryPath; + + if (FSFindTrackerSettingsDir(&directoryPath) != B_OK) + return; + + BPath path(directoryPath); + path.Append(kAutoMounterSettings); + fPrefsFile.SetTo(path.Path(), O_RDWR); + + if (fPrefsFile.InitCheck() != B_OK) { + // no prefs file yet, create a new one + + BDirectory dir(directoryPath.Path()); + dir.CreateFile(kAutoMounterSettings, &fPrefsFile); + return; + } + + ssize_t settingsSize = (ssize_t)fPrefsFile.Seek(0, SEEK_END); + if (settingsSize == 0) + return; + + ASSERT(settingsSize != 0); + char *buffer = new char[settingsSize]; + + fPrefsFile.Seek(0, 0); + if (fPrefsFile.Read(buffer, (size_t)settingsSize) != settingsSize) { + PRINT(("error reading automounter settings\n")); + delete [] buffer; + return; + } + + BMessage message('stng'); + status_t result = message.Unflatten(buffer); + + if (result != B_OK) { + PRINT(("error %s unflattening settings, size %d\n", strerror(result), + settingsSize)); + delete [] buffer; + return; + } + + delete [] buffer; +// PRINT(("done unflattening settings\n")); + SetParams(&message, true); +#endif +} + + +void +AutoMounter::WriteSettings() +{ +#if _INCLUDES_CLASS_DEVICE_MAP + if (fPrefsFile.InitCheck() != B_OK) + return; + + BMessage message('stng'); + GetSettings(&message); + + ssize_t settingsSize = message.FlattenedSize(); + + char *buffer = new char[settingsSize]; + status_t result = message.Flatten(buffer, settingsSize); + + fPrefsFile.Seek(0, 0); + result = fPrefsFile.Write(buffer, (size_t)settingsSize); + + if (result != settingsSize) + PRINT(("error writing settings, %s\n", strerror(result))); + + delete [] buffer; +#endif +} + + +void +AutoMounter::GetSettings(BMessage *_DEVICE_MAP_ONLY(message)) +{ +#if _INCLUDES_CLASS_DEVICE_MAP + message->AddBool("checkRemovableOnly", fScanParams.removableOrUnknownOnly); + message->AddBool("checkCDs", fScanParams.checkCDROMs); + message->AddBool("checkFloppies", fScanParams.checkFloppies); + message->AddBool("checkOtherRemovables", fScanParams.checkOtherRemovable); + message->AddBool("autoMountRemovableOnly", fAutomountParams.mountRemovableDisksOnly); + message->AddBool("autoMountAll", fAutomountParams.mountAllFS); + message->AddBool("autoMountAllBFS", fAutomountParams.mountBFS); + message->AddBool("autoMountAllHFS", fAutomountParams.mountHFS); + message->AddBool("initialMountAll", fInitialMountAll); + message->AddBool("initialMountAllBFS", fInitialMountAllBFS); + message->AddBool("initialMountRestore", fInitialMountRestore); + message->AddBool("initialMountAllHFS", fInitialMountAllHFS); + message->AddBool("suspended", fSuspended); + + // Save mounted volumes so we can optionally mount them on next + // startup + BVolumeRoster volumeRoster; + BVolume volume; + while (volumeRoster.GetNextVolume(&volume) == B_OK) { + fs_info info; + if (fs_stat_dev(volume.Device(), &info) == 0 + && info.flags & (B_FS_IS_REMOVABLE | B_FS_IS_PERSISTENT)) + message->AddString(info.device_name, info.volume_name); + } +#endif +} + + +void +AutoMounter::SetParams(BMessage *_DEVICE_MAP_ONLY(message), + bool _DEVICE_MAP_ONLY(rescan)) +{ +#if _INCLUDES_CLASS_DEVICE_MAP + bool result; + if (message->FindBool("checkRemovableOnly", &result) == B_OK) + fScanParams.removableOrUnknownOnly = result; + if (message->FindBool("checkCDs", &result) == B_OK) + fScanParams.checkCDROMs = result; + if (message->FindBool("checkFloppies", &result) == B_OK) + fScanParams.checkFloppies = result; + if (message->FindBool("checkOtherRemovables", &result) == B_OK) + fScanParams.checkOtherRemovable = result; + if (message->FindBool("autoMountRemovableOnly", &result) == B_OK) + fAutomountParams.mountRemovableDisksOnly = result; + if (message->FindBool("autoMountAll", &result) == B_OK) + fAutomountParams.mountAllFS = result; + if (message->FindBool("autoMountAllBFS", &result) == B_OK) + fAutomountParams.mountBFS = result; + if (message->FindBool("autoMountAllHFS", &result) == B_OK) + fAutomountParams.mountHFS = result; + if (message->FindBool("initialMountAll", &result) == B_OK) + fInitialMountAll = result; + if (message->FindBool("initialMountAllBFS", &result) == B_OK) + fInitialMountAllBFS = result; + if (message->FindBool("initialMountRestore", &result) == B_OK) { + fInitialMountRestore = result; + if (fInitialMountRestore) + gSettingsMessage = *message; + } + if (message->FindBool("initialMountAllHFS", &result) == B_OK) + fInitialMountAllHFS = result; + + if (message->FindBool("suspended", &result) == B_OK) + SuspendResume(result); + + if (rescan) + CheckVolumesNow(); +#endif +} + diff --git a/src/kits/tracker/AutoMounter.h b/src/kits/tracker/AutoMounter.h new file mode 100644 index 0000000000..34c3e8497b --- /dev/null +++ b/src/kits/tracker/AutoMounter.h @@ -0,0 +1,153 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef _AUTO_MOUNTER_H +#define _AUTO_MOUNTER_H + +#include +#include +#include + +#if OPEN_TRACKER +#include "DeviceMap.h" +#else +#include +#endif + +namespace BPrivate { + +const uint32 kSetAutomounterParams = 'pmst'; +const uint32 kSuspendAutomounter = 'amsp'; +const uint32 kResumeAutomounter = 'amsr'; +const uint32 kMountAllNow = 'mntn'; +const uint32 kMountVolume = 'mntv'; +const uint32 kTryMountingFloppy = 'mntf'; +const uint32 kAutomounterRescan = 'rscn'; + +const int32 kFloppyID = -1; + +struct AutomountParams { + bool mountAllFS; + bool mountBFS; + bool mountHFS; + bool mountRemovableDisksOnly; +}; + +class AutoMounter : public BLooper { +public: + AutoMounter( + bool checkRemovableOnly = true, // do not poll nonremovable disks + bool checkCDs = true, // currently ignored + bool checkFloppies = false, // + bool checkOtherRemovables = true, // currently ignored + bool autoMountRemovableOnly = true, // if false, automount nonremovables too + bool autoMountAll = false, // disregard the file system during autoumont + bool autoMountAllBFS = true, // automount bfs disks + bool autoMountAllHFS = false, // automount hfs diska + bool initialMountAll = false, // mount everything during boot + bool initialMountAllBFS = true, // mount every bfs volume during boot + bool initialMountRestore = false, // restore volumes that were mounted at last shutdown + bool initialMountAllHFS = false); // mount every hfs volume during boot + virtual ~AutoMounter(); + virtual bool QuitRequested(); + + void GetSettings(BMessage *); + + void CheckVolumesNow(); + // mounts everything respecting the current automounting settings + // used to sync up right after settings changed + + void EachMountableItemAndFloppy(EachPartitionFunction , void *); + void EachMountedItem(EachPartitionFunction, void *); + Partition * EachPartition(EachPartitionFunction, void *); + Partition *FindPartition(dev_t id); + +private: + void ReadSettings(); + void WriteSettings(); + + virtual void MessageReceived(BMessage *); + + void UnmountAndEjectVolume(BMessage *); + void SetParams(BMessage *, bool rescan); + + static status_t WatchVolumeBinder(void *); + void WatchVolumes(); + + static status_t InitialRescanBinder(void *); + void InitialRescan(); + + void SuspendResume(bool); + + void MountAllNow(); + // used by the mount all now button + // ignores the automounting settings and mounts everything it can + + void MountVolume(BMessage *); + + void RescanDevices(); + + + void TryMountingFloppy(); + bool IsFloppyMounted(); + bool FloppyInList(); + +#if _INCLUDES_CLASS_DEVICE_MAP + DeviceList fList; +#endif + + // automounter settings + DeviceScanParams fScanParams; + AutomountParams fAutomountParams; + + bool fInitialMountAll; + bool fInitialMountAllBFS; + bool fInitialMountRestore; + bool fInitialMountAllHFS; + bool fSuspended; + + // misc. + thread_id fScanThread; + volatile bool fQuitting; + + BFile fPrefsFile; +}; + +Partition *AddMountableItemToMessage(Partition *, void *); + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/AutoMounterSettings.cpp b/src/kits/tracker/AutoMounterSettings.cpp new file mode 100644 index 0000000000..3ecf388455 --- /dev/null +++ b/src/kits/tracker/AutoMounterSettings.cpp @@ -0,0 +1,261 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include +#include +#include +#include +#include + +#include "AutoMounter.h" +#include "AutoMounterSettings.h" + +const uint32 kDone = 'done'; +const uint32 kMountAllNow = 'done'; +const uint32 kAutomountSettingsChanged = 'achg'; +const uint32 kBootMountSettingsChanged = 'bchg'; +const uint32 kAutoAll = 'aall'; +const uint32 kAutoBFS = 'abfs'; +const uint32 kAutoHFS = 'ahfs'; +const uint32 kInitAll = 'iall'; +const uint32 kInitBFS = 'ibfs'; +const uint32 kInitHFS = 'ihfs'; + +const BPoint kButtonSize(80, 20); +const BPoint kSmallButtonSize(60, 20); +const rgb_color kLightGray = { 216, 216, 216, 255}; + +const int32 kCheckBoxSpacing = 20; + +AutomountSettingsDialog *AutomountSettingsDialog::oneCopyOnly = NULL; + +void +AutomountSettingsDialog::RunAutomountSettings(AutoMounter *target) +{ + // either activate an existing mount settings dialog or create a new one + if (oneCopyOnly) { + oneCopyOnly->Activate(); + return; + } + + BMessage message; + target->GetSettings(&message); + (new AutomountSettingsDialog(&message, target))->Show(); +} + +AutomountSettingsPanel::AutomountSettingsPanel(BRect frame, + BMessage *settings, AutoMounter *target) + : BBox(frame, "", B_FOLLOW_ALL, B_WILL_DRAW | B_FRAME_EVENTS + | B_NAVIGABLE_JUMP, B_PLAIN_BORDER), + fTarget(target) +{ + SetViewColor(kLightGray); + + BRect checkBoxRect(Bounds()); + + BRect boxRect(Bounds()); + boxRect.InsetBy(10, 15); + boxRect.bottom = boxRect.top + 85; + BBox *box = new BBox(boxRect, "autoMountBox", B_FOLLOW_ALL, + B_WILL_DRAW | B_FRAME_EVENTS | B_PULSE_NEEDED | B_NAVIGABLE_JUMP); + box->SetLabel("Automatic Disk Mounting:"); + AddChild(box); + + checkBoxRect = box->Bounds(); + checkBoxRect.InsetBy(10, 18); + + checkBoxRect.bottom = checkBoxRect.top + 20; + + scanningDisabledCheck = new BRadioButton(checkBoxRect, "scanningOff", + "Don't Automount", new BMessage(kAutomountSettingsChanged)); + box->AddChild(scanningDisabledCheck); + + checkBoxRect.OffsetBy(0, kCheckBoxSpacing); + autoMountAllBFSCheck = new BRadioButton(checkBoxRect, "autoBFS", + "All BeOS Disks", new BMessage(kAutomountSettingsChanged)); + box->AddChild(autoMountAllBFSCheck); + + checkBoxRect.OffsetBy(0, kCheckBoxSpacing); + autoMountAllCheck = new BRadioButton(checkBoxRect, "autoAll", + "All Disks", new BMessage(kAutomountSettingsChanged)); + box->AddChild(autoMountAllCheck); + + + boxRect.OffsetTo(boxRect.left, boxRect.bottom + 15); + boxRect.bottom = boxRect.top + 105; + box = new BBox(boxRect, "", B_FOLLOW_ALL, B_WILL_DRAW | B_FRAME_EVENTS + | B_PULSE_NEEDED | B_NAVIGABLE_JUMP); + box->SetLabel("Disk Mounting During Boot:"); + AddChild(box); + + checkBoxRect = box->Bounds(); + checkBoxRect.InsetBy(10, 18); + + checkBoxRect.bottom = checkBoxRect.top + 20; + initialDontMountCheck = new BRadioButton(checkBoxRect, "initialNone", + "Only The Boot Disk", new BMessage(kBootMountSettingsChanged)); + box->AddChild(initialDontMountCheck); + + checkBoxRect.OffsetBy(0, kCheckBoxSpacing); + initialMountRestoreCheck = new BRadioButton(checkBoxRect, "initialRestore", + "Previously Mounted Disks", new BMessage(kBootMountSettingsChanged)); + box->AddChild(initialMountRestoreCheck); + + checkBoxRect.OffsetBy(0, kCheckBoxSpacing); + initialMountAllBFSCheck = new BRadioButton(checkBoxRect, "initialBFS", + "All BeOS Disks", new BMessage(kBootMountSettingsChanged)); + box->AddChild(initialMountAllBFSCheck); + + checkBoxRect.OffsetBy(0, kCheckBoxSpacing); + initialMountAllCheck = new BRadioButton(checkBoxRect, "initialAll", + "All Disks", new BMessage(kBootMountSettingsChanged)); + box->AddChild(initialMountAllCheck); + + + BRect buttonRect(Bounds()); + buttonRect.InsetBy(15, 15); + buttonRect.SetLeftTop(buttonRect.RightBottom() - kSmallButtonSize); + fDone = new BButton(buttonRect, "done", "Done", new BMessage(kDone)); + + buttonRect.OffsetTo(buttonRect.left - 15 - buttonRect.Width(), buttonRect.top); + buttonRect.left = buttonRect.left - 60; + fMountAllNow = new BButton(buttonRect, "mountAll", "Mount all disks now", + new BMessage(kMountAllNow)); + + AddChild(fMountAllNow); + + AddChild(fDone); + fDone->MakeDefault(true); + + bool result; + if (settings->FindBool("autoMountAll", &result) == B_OK && result) + autoMountAllCheck->SetValue(1); + else if (settings->FindBool("autoMountAllBFS", &result) == B_OK && result) + autoMountAllBFSCheck->SetValue(1); + else + scanningDisabledCheck->SetValue(1); + + if (settings->FindBool("suspended", &result) == B_OK && result) + scanningDisabledCheck->SetValue(1); + + if (settings->FindBool("initialMountAll", &result) == B_OK && result) + initialMountAllCheck->SetValue(1); + else if (settings->FindBool("initialMountRestore", &result) == B_OK && result) + initialMountRestoreCheck->SetValue(1); + else if (settings->FindBool("initialMountAllBFS", &result) == B_OK && result) + initialMountAllBFSCheck->SetValue(1); + else + initialDontMountCheck->SetValue(1); + +} + +AutomountSettingsPanel::~AutomountSettingsPanel() +{ +} + +void +AutomountSettingsPanel::SendSettings(bool rescan) +{ + BMessage message(kSetAutomounterParams); + + message.AddBool("autoMountAll", (bool)autoMountAllCheck->Value()); + message.AddBool("autoMountAllBFS", (bool)autoMountAllBFSCheck->Value()); + if (autoMountAllBFSCheck->Value()) + message.AddBool("autoMountAllHFS", false); + + message.AddBool("suspended", (bool)scanningDisabledCheck->Value()); + message.AddBool("rescanNow", rescan); + + message.AddBool("initialMountAll", (bool)initialMountAllCheck->Value()); + message.AddBool("initialMountAllBFS", (bool)initialMountAllBFSCheck->Value()); + message.AddBool("initialMountRestore", (bool)initialMountRestoreCheck->Value()); + if (initialDontMountCheck->Value()) + message.AddBool("initialMountAllHFS", false); + + fTarget->PostMessage(&message, NULL); +} + +void +AutomountSettingsPanel::AttachedToWindow() +{ + initialMountAllCheck->SetTarget(this); + initialMountAllBFSCheck->SetTarget(this); + initialMountRestoreCheck->SetTarget(this); + initialDontMountCheck->SetTarget(this); + autoMountAllCheck->SetTarget(this); + autoMountAllBFSCheck->SetTarget(this); + scanningDisabledCheck->SetTarget(this); + fDone->SetTarget(this); + fMountAllNow->SetTarget(fTarget); +} + +void +AutomountSettingsPanel::MessageReceived(BMessage *message) +{ + switch (message->what) { + case kDone: + case B_QUIT_REQUESTED: + Window()->Quit(); + break; + + case kAutomountSettingsChanged: + SendSettings(true); + break; + + case kBootMountSettingsChanged: + SendSettings(false); + break; + + default: + _inherited::MessageReceived(message); + break; + } +} + +AutomountSettingsDialog::AutomountSettingsDialog(BMessage *settings, + AutoMounter *target) + : BWindow(BRect(100, 100, 320, 370), "Disk Mount Settings", + B_TITLED_WINDOW, B_NOT_RESIZABLE | B_NOT_ZOOMABLE) +{ + AddChild(new AutomountSettingsPanel(Bounds(), settings, target)); + ASSERT(!oneCopyOnly); + oneCopyOnly = this; +} + +AutomountSettingsDialog::~AutomountSettingsDialog() +{ + ASSERT(oneCopyOnly); + oneCopyOnly = NULL; +} + diff --git a/src/kits/tracker/AutoMounterSettings.h b/src/kits/tracker/AutoMounterSettings.h new file mode 100644 index 0000000000..143d32cf23 --- /dev/null +++ b/src/kits/tracker/AutoMounterSettings.h @@ -0,0 +1,93 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef __AUTOMOUNTER_SETTINGS__ +#define __AUTOMOUNTER_SETTINGS__ + +#include +#include + +class BCheckBox; +class BRadioButton; +class BButton; + +namespace BPrivate { + +class AutoMounter; + +class AutomountSettingsPanel : public BBox { +public: + AutomountSettingsPanel(BRect, BMessage *, AutoMounter *); + virtual ~AutomountSettingsPanel(); + +protected: + virtual void MessageReceived(BMessage *); + virtual void AttachedToWindow(); + +private: + void SendSettings(bool rescan); + + BRadioButton *initialDontMountCheck; + BRadioButton *initialMountAllBFSCheck; + BRadioButton *initialMountAllCheck; + BRadioButton *initialMountRestoreCheck; + + BRadioButton *scanningDisabledCheck; + BRadioButton *autoMountAllBFSCheck; + BRadioButton *autoMountAllCheck; + + BButton *fDone; + BButton *fMountAllNow; + + AutoMounter *fTarget; + + typedef BBox _inherited; +}; + +class AutomountSettingsDialog : public BWindow { +public: + AutomountSettingsDialog(BMessage *settings, AutoMounter *); + virtual ~AutomountSettingsDialog(); + + static void RunAutomountSettings(AutoMounter *); + +private: + static AutomountSettingsDialog *oneCopyOnly; +}; + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/Background.h b/src/kits/tracker/Background.h new file mode 100644 index 0000000000..fab97201c6 --- /dev/null +++ b/src/kits/tracker/Background.h @@ -0,0 +1,70 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef _TRACKER_BACKGROUND_H +#define _TRACKER_BACKGROUND_H + +#include + +/*----------------------------------------------------------------*/ +/*----- Tracker background attribute name ----------------------*/ + +#define B_BACKGROUND_INFO "be:bgndimginfo" + +/*----------------------------------------------------------------*/ +/*----- Tracker background BMessage entries --------------------*/ + +#define B_BACKGROUND_IMAGE "be:bgndimginfopath" // string path +#define B_BACKGROUND_MODE "be:bgndimginfomode" // int32, the enum below +#define B_BACKGROUND_ORIGIN "be:bgndimginfooffset" // BPoint +#define B_BACKGROUND_ERASE_TEXT "be:bgndimginfoerasetext" // bool +#define B_BACKGROUND_WORKSPACES "be:bgndimginfoworkspaces" // uint32 + +/*----------------------------------------------------------------*/ +/*----- Background mode values ---------------------------------*/ + +enum { + B_BACKGROUND_MODE_USE_ORIGIN, + B_BACKGROUND_MODE_CENTERED, // only works on Desktop + B_BACKGROUND_MODE_SCALED, // only works on Desktop + B_BACKGROUND_MODE_TILED +}; + +/*----------------------------------------------------------------*/ +/*----------------------------------------------------------------*/ + +const int32 B_RESTORE_BACKGROUND_IMAGE = 'Tbgr'; // force a Tracker window to + // use a new background image + +#endif /* _TRACKER_BACKGROUND_H */ diff --git a/src/kits/tracker/BackgroundImage.cpp b/src/kits/tracker/BackgroundImage.cpp new file mode 100644 index 0000000000..6cea494328 --- /dev/null +++ b/src/kits/tracker/BackgroundImage.cpp @@ -0,0 +1,317 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// Classes used for setting up and managing background images +// + +#include +#include +#include +#include +#include + +#include + +#include "BackgroundImage.h" +#include "Commands.h" +#include "PoseView.h" + +namespace BPrivate { + +const char *kBackgroundImageInfo = "be:bgndimginfo"; +const char *kBackgroundImageInfoOffset = "be:bgndimginfooffset"; +const char *kBackgroundImageInfoEraseText = "be:bgndimginfoerasetext"; +const char *kBackgroundImageInfoMode = "be:bgndimginfomode"; +const char *kBackgroundImageInfoWorkspaces = "be:bgndimginfoworkspaces"; +const char *kBackgroundImageInfoPath = "be:bgndimginfopath"; + +} + +BackgroundImage * +BackgroundImage::GetBackgroundImage(const BNode *node, bool isDesktop) +{ + attr_info info; + if (node->GetAttrInfo(kBackgroundImageInfo, &info) != B_OK) + return NULL; + + BMessage container; + char *buffer = new char [info.size]; + + status_t error = node->ReadAttr(kBackgroundImageInfo, info.type, 0, buffer, (size_t)info.size); + if (error == info.size) + error = container.Unflatten(buffer); + + delete [] buffer; + + if (error != B_OK) + return NULL; + + BackgroundImage *result = NULL; + for (int32 index = 0; ; index++) { + const char *path; + uint32 workspaces = B_ALL_WORKSPACES; + Mode mode = kTiled; + bool eraseTextWidgetBackground = true; + BPoint offset; + + if (container.FindString(kBackgroundImageInfoPath, index, &path) != B_OK) + break; + + BBitmap *bitmap = BTranslationUtils::GetBitmap(path); + if (!bitmap) { +// PRINT(("failed to load background bitmap from path\n")); + continue; + } + + container.FindInt32(kBackgroundImageInfoWorkspaces, index, (int32 *)&workspaces); + container.FindInt32(kBackgroundImageInfoMode, index, (int32 *)&mode); + container.FindBool(kBackgroundImageInfoEraseText, index, &eraseTextWidgetBackground); + container.FindPoint(kBackgroundImageInfoOffset, index, &offset); + + BackgroundImage::BackgroundImageInfo *imageInfo = new + BackgroundImage::BackgroundImageInfo(workspaces, bitmap, mode, offset, + eraseTextWidgetBackground); + + if (!result) + result = new BackgroundImage(node, isDesktop); + + result->Add(imageInfo); + } + return result; +} + + +BackgroundImage::BackgroundImageInfo::BackgroundImageInfo(uint32 workspaces, + BBitmap *bitmap, Mode mode, BPoint offset, bool eraseTextWidget) + : fWorkspace(workspaces), + fBitmap(bitmap), + fMode(mode), + fOffset(offset), + fEraseTextWidgetBackground(eraseTextWidget) +{ +} + + +BackgroundImage::BackgroundImageInfo::~BackgroundImageInfo() +{ + delete fBitmap; +} + + +BackgroundImage::BackgroundImage(const BNode *node, bool desktop) + : fIsDesktop(desktop), + fDefinedByNode(*node), + fView(NULL), + fShowingBitmap(NULL), + fBitmapForWorkspaceList(1, true) +{ +} + +BackgroundImage::~BackgroundImage() +{ +} + + +void +BackgroundImage::Add(BackgroundImageInfo *info) +{ + fBitmapForWorkspaceList.AddItem(info); +} + +void +BackgroundImage::Show(BView *view, int32 workspace) +{ + fView = view; + + BackgroundImageInfo *info = ImageInfoForWorkspace(workspace); + if (info) { + BPoseView *poseView = dynamic_cast(fView); + if (poseView) + poseView->SetEraseWidgetTextBackground(info->fEraseTextWidgetBackground); + Show(info, fView); + } +} + +void +BackgroundImage::Show(BackgroundImageInfo *info, BView *view) +{ + BRect viewBounds(view->Bounds()); + BRect bitmapBounds(info->fBitmap->Bounds()); + BRect destinationBitmapBounds(bitmapBounds); + + uint32 tile = 0; + uint32 followFlags = B_FOLLOW_TOP | B_FOLLOW_LEFT; + + // figure out the display mode and the destination bounds for the bitmap + switch (info->fMode) { + case kCentered: + if (fIsDesktop) { + destinationBitmapBounds.OffsetBy( + (viewBounds.Width() - bitmapBounds.Width()) / 2, + (viewBounds.Height() - bitmapBounds.Height()) / 2); + break; + } + // else fall thru + case kScaledToFit: + if (fIsDesktop) { + destinationBitmapBounds = viewBounds; + followFlags = B_FOLLOW_ALL; + break; + } + // else fall thru + case kAtOffset: + destinationBitmapBounds.OffsetTo(info->fOffset); + break; + case kTiled: + if (fIsDesktop) { + destinationBitmapBounds.OffsetBy( + (viewBounds.Width() - bitmapBounds.Width()) / 2, + (viewBounds.Height() - bitmapBounds.Height()) / 2); + } + tile = B_TILE_BITMAP; + break; + } + + BPoseView *poseView = dynamic_cast(view); + if (poseView) + poseView->SetEraseWidgetTextBackground(info->fEraseTextWidgetBackground); + + // switch to the bitmap and force a redraw + view->SetViewBitmap(info->fBitmap, bitmapBounds, destinationBitmapBounds, + followFlags, tile); + view->Invalidate(); + + fShowingBitmap = info; +} + +void +BackgroundImage::Remove() +{ + if (fShowingBitmap) { + fView->ClearViewBitmap(); + fView->Invalidate(); + BPoseView *poseView = dynamic_cast(fView); + // make sure text widgets draw the default way, erasing their background + if (poseView) + poseView->SetEraseWidgetTextBackground(true); + } + fShowingBitmap = NULL; +} + +BackgroundImage::BackgroundImageInfo * +BackgroundImage::ImageInfoForWorkspace(int32 workspace) const +{ + uint32 workspaceMask = 1; + + for ( ; workspace; workspace--) + workspaceMask *= 2; + + int32 count = fBitmapForWorkspaceList.CountItems(); + + // do a simple lookup for the most likely candidate bitmap - + // pick the imageInfo that is only defined for this workspace over one + // that supports multiple workspaces + BackgroundImageInfo *result = NULL; + for (int32 index = 0; index < count; index++) { + BackgroundImageInfo *info = fBitmapForWorkspaceList.ItemAt(index); + if (info->fWorkspace == workspaceMask) + return info; + if (info->fWorkspace & workspaceMask) + result = info; + } + + return result; +} + +void +BackgroundImage::WorkspaceActivated(BView *view, int32 workspace, bool state) +{ + if (!fIsDesktop) + // we only care for desktop bitmaps + return; + + if (!state) + // we only care comming into a new workspace, not leaving one + return; + + BackgroundImageInfo *info = ImageInfoForWorkspace(workspace); + if (info != fShowingBitmap) { + if (info) + Show(info, view); + else { + if (BPoseView *poseView = dynamic_cast(view)) + poseView->SetEraseWidgetTextBackground(true); + view->ClearViewBitmap(); + view->Invalidate(); + } + fShowingBitmap = info; + } +} + +void +BackgroundImage::ScreenChanged(BRect, color_space) +{ + if (!fIsDesktop || !fShowingBitmap) + return; + + if (fShowingBitmap->fMode == kCentered) { + BRect viewBounds(fView->Bounds()); + BRect bitmapBounds(fShowingBitmap->fBitmap->Bounds()); + BRect destinationBitmapBounds(bitmapBounds); + destinationBitmapBounds.OffsetBy( + (viewBounds.Width() - bitmapBounds.Width()) / 2, + (viewBounds.Height() - bitmapBounds.Height()) / 2); + + fView->SetViewBitmap(fShowingBitmap->fBitmap, bitmapBounds, destinationBitmapBounds, + B_FOLLOW_NONE, 0); + fView->Invalidate(); + } +} + +BackgroundImage * +BackgroundImage::Refresh(BackgroundImage *oldBackgroundImage, + const BNode *fromNode, bool desktop, BPoseView *poseView) +{ + if (oldBackgroundImage) { + oldBackgroundImage->Remove(); + delete oldBackgroundImage; + } + + BackgroundImage *result = GetBackgroundImage(fromNode, desktop); + if (result && poseView->ViewMode() != kListMode) + result->Show(poseView, current_workspace()); + + return result; +} + diff --git a/src/kits/tracker/BackgroundImage.h b/src/kits/tracker/BackgroundImage.h new file mode 100644 index 0000000000..75c20277cc --- /dev/null +++ b/src/kits/tracker/BackgroundImage.h @@ -0,0 +1,130 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// Classes used for setting up and managing background images +// + +#ifndef __BACKGROUND_IMAGE__ +#define __BACKGROUND_IMAGE__ + +#include +#include "ObjectList.h" + +class BNode; +class BView; +class BBitmap; + +namespace BPrivate { + +class BackgroundImage; +class BPoseView; + +extern const char *kBackgroundImageInfo; +extern const char *kBackgroundImageInfoOffset; +extern const char *kBackgroundImageInfoEraseText; +extern const char *kBackgroundImageInfoMode; +extern const char *kBackgroundImageInfoWorkspaces; +extern const char *kBackgroundImageInfoPath; + +const uint32 kRestoreBackgroundImage = 'Tbgr'; + +class BackgroundImage { + // This class knows everything about which bitmap to use for a given + // view and how. + // Unlike other windows, the Desktop window can have different backgrounds + // for each workspace +public: + + enum Mode { + kAtOffset, + kCentered, // only works on Desktop + kScaledToFit, // only works on Desktop + kTiled + }; + + class BackgroundImageInfo { + // element of the per-workspace list + public: + BackgroundImageInfo(uint32 workspace, BBitmap *bitmap, Mode mode, BPoint offset, + bool eraseTextWidget); + ~BackgroundImageInfo(); + + uint32 fWorkspace; + BBitmap *fBitmap; + Mode fMode; + BPoint fOffset; + bool fEraseTextWidgetBackground; + }; + + static BackgroundImage *GetBackgroundImage(const BNode *, bool isDesktop); + // create a BackgroundImage object by reading it from a node + virtual ~BackgroundImage(); + + void Show(BView *view, int32 workspace); + // display the right background for a given workspace + void Remove(); + // remove the background from it's current view + + void WorkspaceActivated(BView *view, int32 workspace, bool state); + // respond to a workspace change + void ScreenChanged(BRect , color_space); + // respond to a screen size change + static BackgroundImage *Refresh(BackgroundImage *oldBackgroundImage, + const BNode *fromNode, bool desktop, BPoseView *poseView); + // respond to a background image setting change + +private: + BackgroundImageInfo *ImageInfoForWorkspace(int32) const; + void Show(BackgroundImageInfo *, BView *view); + + BackgroundImage(const BNode *, bool); + // no public constructor, GetBackgroundImage factory function is + // used instead + + void Add(BackgroundImageInfo *); + + bool fIsDesktop; + BNode fDefinedByNode; + BView *fView; + BackgroundImageInfo *fShowingBitmap; + + BObjectList fBitmapForWorkspaceList; +}; + + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/Bitmaps.cpp b/src/kits/tracker/Bitmaps.cpp new file mode 100644 index 0000000000..1da3e1e4ac --- /dev/null +++ b/src/kits/tracker/Bitmaps.cpp @@ -0,0 +1,226 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include "Bitmaps.h" +#include "Utilities.h" + +BImageResources::BImageResources(void *memAddr) +{ + image_id image = find_image(memAddr); + image_info info; + if (get_image_info(image, &info) == B_OK) { +#if _SUPPORTS_RESOURCES + BFile file(&info.name[0], B_READ_ONLY); +#else + BString name(&info.name[0]); + name += ".rsrc"; + BFile file(name.String(), B_READ_ONLY); +#endif + if (file.InitCheck() == B_OK) + fResources.SetTo(&file); + } +} + +BImageResources::~BImageResources() +{ +} + +const BResources * +BImageResources::ViewResources() const +{ + if (fLock.Lock() != B_OK) + return NULL; + + return &fResources; +} + +BResources * +BImageResources::ViewResources() +{ + if (fLock.Lock() != B_OK) + return NULL; + + return &fResources; +} + +status_t +BImageResources::FinishResources(BResources *res) const +{ + ASSERT(res == &fResources); + if (res != &fResources) + return B_BAD_VALUE; + + fLock.Unlock(); + return B_OK; +} + +const void * +BImageResources::LoadResource(type_code type, int32 id, size_t *out_size) const +{ + // Serialize execution. + // Looks like BResources is not really thread safe. We should + // clean that up in the future and remove the locking from here. + BAutolock lock(fLock); + if (!lock.IsLocked()) + return 0; + + // Return the resource. Because we never change the BResources + // object, the returned data will not change until TTracker is + // destroyed. + return const_cast(&fResources)->LoadResource(type, id, out_size); +} + +const void * +BImageResources::LoadResource(type_code type, const char *name, size_t *out_size) const +{ + // Serialize execution. + BAutolock lock(fLock); + if (!lock.IsLocked()) + return NULL; + + // Return the resource. Because we never change the BResources + // object, the returned data will not change until TTracker is + // destroyed. + return const_cast(&fResources)->LoadResource(type, name, out_size); +} + +status_t +BImageResources::GetIconResource(int32 id, icon_size size, BBitmap *dest) const +{ + if (size != B_LARGE_ICON && size != B_MINI_ICON ) + return B_ERROR; + + size_t len = 0; + const void *data = LoadResource(size == B_LARGE_ICON ? 'ICON' : 'MICN', + id, &len); + + if (data == 0 || len != (size_t)(size == B_LARGE_ICON ? 1024 : 256)) { + TRESPASS(); + return B_ERROR; + } + + dest->SetBits(data, (int32)len, 0, kDefaultIconDepth); + return B_OK; +} + +image_id +BImageResources::find_image(void *memAddr) const +{ + image_info info; + int32 cookie = 0; + while (get_next_image_info(0, &cookie, &info) == B_OK) + if ((info.text <= memAddr && (((uint8 *)info.text)+info.text_size) > memAddr) + ||(info.data <= memAddr && (((uint8 *)info.data)+info.data_size) > memAddr)) + // Found the image. + return info.id; + + return -1; +} + +status_t +BImageResources::GetBitmapResource(type_code type, int32 id, BBitmap **out) const +{ + *out = NULL; + + size_t len = 0; + const void *data = LoadResource(type, id, &len); + + if (data == NULL) { + TRESPASS(); + return B_ERROR; + } + + BMemoryIO stream(data, len); + + // Try to read as an archived bitmap. + stream.Seek(0, SEEK_SET); + BMessage archive; + status_t err = archive.Unflatten(&stream); + if (err != B_OK) + return err; + + *out = new BBitmap(&archive); + if (!*out) + return B_ERROR; + + err = (*out)->InitCheck(); + if (err != B_OK) { + delete *out; + *out = NULL; + } + + return err; +} + + +static BLocker resLock; +static BImageResources *resources = NULL; + +// This class is used as a static instance to delete the resources +// global object when the image is getting unloaded. +class _TTrackerCleanupResources +{ +public: + _TTrackerCleanupResources() { } + ~_TTrackerCleanupResources() + { + delete resources; + resources = NULL; + } +}; + + +namespace BPrivate { + +static _TTrackerCleanupResources CleanupResources; + + +BImageResources *GetTrackerResources() +{ + if (!resources) { + BAutolock lock(&resLock); + resources = new BImageResources(&resources); + } + return resources; +} + +} diff --git a/src/kits/tracker/Bitmaps.h b/src/kits/tracker/Bitmaps.h new file mode 100644 index 0000000000..0084d9eeb4 --- /dev/null +++ b/src/kits/tracker/Bitmaps.h @@ -0,0 +1,154 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef __BITS__ +#define __BITS__ + + +#include +#include +#include +#include + +#include "TrackerIcons.h" + +class BBitmap; + +namespace BPrivate { + +// This maps the old tracker resource ID definitions to the new ones +// generated by QuickRes. Somebody want to go through the code and +// get rid of all these? +enum { + kResAppIcon = R_AppIcon, + kResFileIcon = R_FileIcon, + kResFolderIcon = 1, + kResTrashIcon = R_TrashIcon, + kResTrashFullIcon = R_TrashFullIcon, + kResQueryIcon = 2, + kResQueryTemplateIcon = 4, + kResPrinterIcon = R_PrinterIcon, + kResBarberPoleBitmap = R_BarberPoleBitmap, + kResFloppyIcon = R_FloppyIcon, + kResHardDiskIcon = 3, + kResCDIcon = R_CDIcon, + kResBeBoxIcon = R_BeBoxIcon, + kResBookmarkIcon = R_BookmarkIcon, + kResPersonIcon = R_PersonIcon, + kResBrokenLinkIcon = R_BrokenLinkIcon, + kResDeskIcon = R_DeskIcon, + kResHomeDirIcon = R_HomeDirIcon, + kResBeosFolderIcon = R_BeosFolderIcon, + kResBootVolumeIcon = R_BootVolumeIcon, + kResFontDirIcon = R_FontDirIcon, + kResAppsDirIcon = R_AppsDirIcon, + kResPrefsDirIcon = R_PrefsDirIcon, + kResMailDirIcon = R_MailDirIcon, + kResQueryDirIcon = R_QueryDirIcon, + kResSpoolFileIcon = R_SpoolFileIcon, + kResGenericPrinterIcon = R_GenericPrinterIcon, + kResDevelopDirIcon = R_DevelopDirIcon, + kResDownloadDirIcon = R_DownloadDirIcon, + kResPersonDirIcon = R_PersonDirIcon, + kResUtilDirIcon = R_UtilDirIcon, + kResConfigDirIcon = R_ConfigDirIcon, + kResMoveStatusBitmap = R_MoveStatusBitmap, + kResCopyStatusBitmap = R_CopyStatusBitmap, + kResTrashStatusBitmap = R_TrashStatusBitmap, + kResBackNavActive = R_ResBackNavActive, + kResBackNavInactive = R_ResBackNavInactive, + kResForwNavActive = R_ResForwNavActive, + kResForwNavInactive = R_ResForwNavInactive, + kResUpNavActive = R_ResUpNavActive, + kResUpNavInactive = R_ResUpNavInactive, + kResBackNavActiveSel = R_ResBackNavActiveSel, + kResForwNavActiveSel = R_ResForwNavActiveSel, + kResUpNavActiveSel = R_ResUpNavActiveSel, + kResShareIcon = R_ShareIcon +}; + + +class BImageResources +{ + // convenience class for accessing +public: + BImageResources(void *memAddr); + ~BImageResources(); + + BResources *ViewResources(); + const BResources *ViewResources() const; + + status_t FinishResources(BResources *) const; + + const void *LoadResource(type_code type, int32 id, + size_t *outSize) const; + const void *LoadResource(type_code type, const char *name, + size_t *outSize) const; + // load a resource from the Tracker executable, just like the + // corresponding functions in BResources. These methods are + // thread-safe. + + status_t GetIconResource(int32 id, icon_size size, BBitmap *dest) const; + // this is a wrapper around LoadResource(), for retrieving + // B_LARGE_ICON and B_MINI_ICON ('ICON' and 'MICN' respectively) + // resources. this does sanity checking on the found data, + // and if all is okay blasts it into the 'dest' bitmap. + + status_t GetBitmapResource(type_code type, int32 id, BBitmap **out) const; + // this is a wrapper around LoadResource(), for retrieving + // arbitrary bitmaps. the resource with the given type and + // id is looked up, and a BBitmap created from it and returned + // in . currently it can only create bitmaps from data + // that is an archived bitmap object. + +private: + image_id find_image(void *memAddr) const; + + mutable BLocker fLock; + BResources fResources; +}; + + + +extern +#if !B_BEOS_VERSION_DANO +_IMPEXP_TRACKER +#endif +BImageResources* GetTrackerResources(); + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/Commands.h b/src/kits/tracker/Commands.h new file mode 100644 index 0000000000..4131ec6bee --- /dev/null +++ b/src/kits/tracker/Commands.h @@ -0,0 +1,142 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef _COMMANDS_H +#define _COMMANDS_H + +#include "PublicCommands.h" + +namespace BPrivate { + +// external app messages + +const uint32 kGetInfo = 'Tinf'; +const uint32 kMoveToTrash = 'Ttrs'; +const uint32 kDelete = 'Tdel'; +const uint32 kRestoreFromTrash = 'Tres'; +const uint32 kIdentifyEntry = 'Tidt'; +const uint32 kOpenSelection = 'Tosl'; +const uint32 kOpenSelectionWith = 'Tosu'; +const uint32 kCloseAllWindows = 'Tall'; +const uint32 kCloseWindowAndChildren = 'Tcwc'; + +// end external app messages + +const uint32 kRestoreState = 'Trst'; + +const uint32 kCutMoreSelectionToClipboard = 'Tmvm'; +const uint32 kCopyMoreSelectionToClipboard = 'Tcpm'; +const uint32 kPasteLinksFromClipboard = 'Tplc'; +const uint32 kCancelSelectionToClipboard = 'Tesc'; +const uint32 kClipboardPosesChanged = 'Tcpc'; + +const uint32 kEditItem = 'Tedt'; +const uint32 kEditQuery = 'Qedt'; +const uint32 kNewFolder = 'Tnwf'; +const uint32 kNewEntryFromTemplate = 'Tnwe'; +const uint32 kCopySelectionTo = 'Tcsl'; +const uint32 kMoveSelectionTo = 'Tmsl'; +const uint32 kCreateLink = 'Tlnk'; +const uint32 kCreateRelativeLink = 'Trln'; +const uint32 kDuplicateSelection = 'Tdsl'; +const uint32 kLoadAddOn = 'Tlda'; +const uint32 kEmptyTrash = 'Tetr'; +const uint32 kAddPrinter = 'Tadp'; +const uint32 kMakeActivePrinter = 'Tmap'; + +const uint32 kUnmountVolume = 'Tunm'; +const uint32 kRunAutomounterSettings = 'Tram'; + +const uint32 kOpenParentDir = 'Topt'; +const uint32 kOpenDir = 'Topd'; +const uint32 kCleanup = 'Tcln'; +const uint32 kCleanupAll = 'Tcla'; +const uint32 kResizeToFit = 'Trtf'; +const uint32 kSelectMatchingEntries = 'Tsme'; +const uint32 kShowSelectionWindow = 'Tssw'; +const uint32 kShowSettingsWindow = 'Tstw'; +const uint32 kInvertSelection = 'Tisl'; + +const uint32 kCancelButton = 'Tcnl'; +const uint32 kDefaultButton = 'Tact'; +const uint32 kPauseButton = 'Tpaw'; +const uint32 kStopButton = 'Tstp'; +const uint32 kCopyAttributes = 'Tcat'; +const uint32 kPasteAttributes = 'Tpat'; +const uint32 kAttributeItem = 'Tatr'; +const uint32 kMIMETypeItem = 'Tmim'; +const uint32 kAddCurrentDir = 'Tadd'; +const uint32 kSwitchDirectory = 'Tswd'; +const uint32 kQuitTracker = 'Tqit'; + +const uint32 kContextMenuDragNDrop = '_dnd'; + +const uint32 kSwitchToHome = 'Tswh'; + +const uint32 kTestIconCache = 'TicC'; + +// Observers and Notifiers: + +// Settings-changed messages: +const uint32 kDisksIconChanged = 'Dicn'; +const uint32 kDesktopIntegrationChanged = 'Dint'; +const uint32 kShowDisksIconChanged = 'Sdic'; +const uint32 kVolumesOnDesktopChanged = 'Codc'; +const uint32 kEjectWhenUnmountingChanged = 'Ewum'; + +const uint32 kWindowsShowFullPathChanged = 'Wsfp'; +const uint32 kSingleWindowBrowseChanged = 'Osmw'; +const uint32 kShowNavigatorChanged = 'Snvc'; +const uint32 kShowSelectionWhenInactiveChanged = 'Sswi'; +const uint32 kTransparentSelectionChanged = 'Trse'; +const uint32 kSortFolderNamesFirstChanged = 'Sfnf'; + +const uint32 kDesktopFilePanelRootChanged = 'Dfpr'; +const uint32 kFavoriteCountChanged = 'Fvct'; +const uint32 kFavoriteCountChangedExternally = 'Fvcx'; + +const uint32 kDateFormatChanged = 'Date'; + +const uint32 kUpdateVolumeSpaceBar = 'UpSB'; +const uint32 kShowVolumeSpaceBar = 'ShSB'; +const uint32 kSpaceBarColorChanged = 'SBcc'; + +const uint32 kDontMoveFilesToTrashChanged = 'STdm'; +const uint32 kAskBeforeDeleteFileChanged = 'STad'; + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/ContainerWindow.cpp b/src/kits/tracker/ContainerWindow.cpp new file mode 100644 index 0000000000..74934250a7 --- /dev/null +++ b/src/kits/tracker/ContainerWindow.cpp @@ -0,0 +1,3756 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "Attributes.h" +#include "AttributeStream.h" +#include "AutoLock.h" +#include "BackgroundImage.h" +#include "Commands.h" +#include "ContainerWindow.h" +#include "DeskWindow.h" +#include "FavoritesMenu.h" +#include "FindPanel.h" +#include "FSClipboard.h" +#include "FSUndoRedo.h" +#include "FSUtils.h" +#include "IconMenuItem.h" +#include "OpenWithWindow.h" +#include "MimeTypes.h" +#include "Model.h" +#include "MountMenu.h" +#include "Navigator.h" +#include "NavMenu.h" +#include "PoseView.h" +#include "SelectionWindow.h" +#include "TitleView.h" +#include "Tracker.h" +#include "TrackerSettings.h" +#include "Thread.h" +#include "TemplatesMenu.h" + + +const uint32 kRedo = 'REDO'; + // this is the same as B_REDO in Dano/Zeta/OpenBeOS + + +#if !B_BEOS_VERSION_DANO +_IMPEXP_BE +#endif +void do_minimize_team(BRect zoomRect, team_id team, bool zoom); + +// Amount you have to move the mouse before a drag starts +const float kDragSlop = 3.0f; + +namespace BPrivate { +const char *kAddOnsMenuName = "Add-Ons"; + +class DraggableContainerIcon : public BView { + public: + DraggableContainerIcon(BRect rect, const char *name, uint32 resizeMask); + + virtual void AttachedToWindow(); + virtual void MouseDown(BPoint where); + virtual void MouseUp(BPoint where); + virtual void MouseMoved(BPoint point, uint32 /*transit*/, const BMessage *message); + virtual void Draw(BRect updateRect); + + private: + uint32 fDragButton; + BPoint fClickPoint; +}; +} // namespace BPrivate + +struct AddOneAddonParams { + BObjectList *primaryList; + BObjectList *secondaryList; +}; + +struct StaggerOneParams { + bool rectFromParent; +}; + +const int32 kContainerWidthMinLimit = 120; +const int32 kContainerWindowHeightLimit = 85; + +const int32 kWindowStaggerBy = 17; + +BRect BContainerWindow::sNewWindRect(85, 50, 415, 280); + + +namespace BPrivate { + +filter_result +ActivateWindowFilter(BMessage *, BHandler **target, BMessageFilter *) +{ + BView *view = dynamic_cast(*target); + + if (view && !dynamic_cast(view) && view->Window()) + view->Window()->Activate(true); + + return B_DISPATCH_MESSAGE; +} + + +static void +StripShortcut(const Model *model, char *result, uint32 &shortcut) +{ + strcpy(result, model->Name()); + + // check if there is a shortcut + uint32 length = strlen(result); + shortcut = '\0'; + if (result[length - 2] == '-') { + shortcut = result[length - 1]; + result[length - 2] = '\0'; + } +} + + +static const Model * +MatchOne(const Model *model, void *castToName) +{ + char buffer[B_FILE_NAME_LENGTH]; + uint32 dummy; + StripShortcut(model, buffer, dummy); + + if (strcmp(buffer, (const char *)castToName) == 0) + // found match, bail out + return model; + + return 0; +} + + +int +CompareLabels(const BMenuItem *item1, const BMenuItem *item2) +{ + return strcasecmp(item1->Label(), item2->Label()); +} + +} // namespace BPrivate + + +static bool +AddOneAddon(const Model *model, const char *name, uint32 shortcut, bool primary, void *context) +{ + AddOneAddonParams *params = (AddOneAddonParams *)context; + + BMessage *message = new BMessage(kLoadAddOn); + message->AddRef("refs", model->EntryRef()); + + ModelMenuItem *item = new ModelMenuItem(model, name, message, + (char)shortcut, B_OPTION_KEY); + + if (primary) + params->primaryList->AddItem(item); + else + params->secondaryList->AddItem(item); + + return false; +} + + +static int32 +AddOnThread(BMessage *refsMessage, entry_ref addonRef, entry_ref dirRef) +{ + std::auto_ptr refsMessagePtr(refsMessage); + + BEntry entry(&addonRef); + BPath path; + status_t result = entry.InitCheck(); + if (result == B_OK) + result = entry.GetPath(&path); + + if (result == B_OK) { + image_id addonImage = load_add_on(path.Path()); + if (addonImage >= 0) { + void (*processRefs)(entry_ref, BMessage *, void *); + result = get_image_symbol(addonImage, "process_refs", 2, (void **)&processRefs); + +#ifndef __INTEL__ + if (result < 0) { + PRINT(("trying old legacy ppc signature\n")); + // try old-style addon signature + result = get_image_symbol(addonImage, + "process_refs__F9entry_refP8BMessagePv", 2, (void **)&processRefs); + } +#endif + + if (result >= 0) { + + // call add-on code + (*processRefs)(dirRef, refsMessagePtr.get(), 0); + + unload_add_on(addonImage); + return B_OK; + } else + PRINT(("couldn't find process_refs\n")); + + unload_add_on(addonImage); + } + } + + char buffer[1024]; + sprintf(buffer, "Error %s loading Add-On %s.", strerror(result), addonRef.name); + + BAlert *alert = new BAlert("", buffer, "Cancel", 0, 0, + B_WIDTH_AS_USUAL, B_WARNING_ALERT); + alert->SetShortcut(0, B_ESCAPE); + alert->Go(); + + return result; +} + + +static bool +NodeHasSavedState(const BNode *node) +{ + attr_info info; + return node->GetAttrInfo(kAttrWindowFrame, &info) == B_OK; +} + + +static bool +OffsetFrameOne(const char *DEBUG_ONLY(name), uint32, off_t, void *castToRect, + void *castToParams) +{ + ASSERT(strcmp(name, kAttrWindowFrame) == 0); + StaggerOneParams *params = (StaggerOneParams *)castToParams; + + if (!params->rectFromParent) + return false; + + if (!castToRect) + return false; + + ((BRect *)castToRect)->OffsetBy(kWindowStaggerBy, kWindowStaggerBy); + return true; +} + + +static void +AddMimeTypeString(BObjectList &list, Model *model) +{ + BString *mimeType = new BString(model->MimeType()); + if (!mimeType->Length() || !mimeType->ICompare(B_FILE_MIMETYPE)) { + // if model is of unknown type, try mimeseting it first + model->Mimeset(true); + mimeType->SetTo(model->MimeType()); + } + + if (mimeType->Length()) { + // only add the type if it's not already there + for (int32 i = list.CountItems(); i-- > 0;) { + BString *string = list.ItemAt(i); + if (string != NULL && !string->ICompare(*mimeType)) + return; + } + list.AddItem(mimeType); + } +} + + +// #pragma mark - + + +DraggableContainerIcon::DraggableContainerIcon(BRect rect, const char *name, + uint32 resizeMask) + : BView(rect, name, resizeMask, B_WILL_DRAW), + fDragButton(0) +{ +} + + +void +DraggableContainerIcon::AttachedToWindow() +{ + //SetViewColor(B_TRANSPARENT_COLOR); + SetViewColor(ui_color(B_MENU_BACKGROUND_COLOR)); +} + + +void +DraggableContainerIcon::MouseDown(BPoint point) +{ + // we only like container windows + BContainerWindow *window = dynamic_cast(Window()); + if (window == NULL) + return; + + // we don't like the Trash icon (because it cannot be moved) + if (window->IsTrash() || window->IsPrintersDir()) + return; + + uint32 buttons; + window->CurrentMessage()->FindInt32("buttons", (int32 *)&buttons); + + if (IconCache::sIconCache->IconHitTest(point, window->TargetModel(), + kNormalIcon, B_MINI_ICON)) { + // The click hit the icon, initiate a drag + fDragButton = buttons & (B_PRIMARY_MOUSE_BUTTON | B_SECONDARY_MOUSE_BUTTON); + fClickPoint = point; + } +} + + +void +DraggableContainerIcon::MouseUp(BPoint /*point*/) +{ + fDragButton = 0; +} + + +void +DraggableContainerIcon::MouseMoved(BPoint point, uint32 /*transit*/, const BMessage */*message*/) +{ + if (fDragButton == 0 + || (abs((int32)(point.x - fClickPoint.x)) <= kDragSlop + && abs((int32)(point.y - fClickPoint.y)) <= kDragSlop)) + return; + + BContainerWindow *window = static_cast(Window()); + // we can only get here in a BContainerWindow + Model *model = window->TargetModel(); + + // Find the required height + BFont font; + GetFont(&font); + + font_height fontHeight; + font.GetHeight(&fontHeight); + float height = fontHeight.ascent + fontHeight.descent + fontHeight.leading + 2 + + Bounds().Height() + 8; + + BRect rect(0, 0, max_c(Bounds().Width(), font.StringWidth(model->Name()) + 4), height); + BBitmap *dragBitmap = new BBitmap(rect, B_RGBA32, true); + + dragBitmap->Lock(); + BView *view = new BView(dragBitmap->Bounds(), "", B_FOLLOW_NONE, 0); + dragBitmap->AddChild(view); + view->SetOrigin(0, 0); + BRect clipRect(view->Bounds()); + BRegion newClip; + newClip.Set(clipRect); + view->ConstrainClippingRegion(&newClip); + + // Transparent draw magic + view->SetHighColor(0, 0, 0, 0); + view->FillRect(view->Bounds()); + view->SetDrawingMode(B_OP_ALPHA); + view->SetHighColor(0, 0, 0, 128); // set the level of transparency by value + view->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_COMPOSITE); + + // Draw the icon + float hIconOffset = (rect.Width() - Bounds().Width()) / 2; + IconCache::sIconCache->Draw(model, view, BPoint(hIconOffset, 0), + kNormalIcon, B_MINI_ICON, true); + + // See if we need to truncate the string + BString nameString = model->Name(); + if (view->StringWidth(model->Name()) > rect.Width()) + view->TruncateString(&nameString, B_TRUNCATE_END, rect.Width() - 5); + + // Draw the label + float leftText = (view->StringWidth(nameString.String()) - Bounds().Width()) / 2; + view->MovePenTo(BPoint(hIconOffset - leftText + 2, Bounds().Height() + (fontHeight.ascent + 2))); + view->DrawString(nameString.String()); + + view->Sync(); + dragBitmap->Unlock(); + + BMessage message(B_SIMPLE_DATA); + message.AddRef("refs", model->EntryRef()); + message.AddPoint("click_pt", fClickPoint); + + BPoint tmpLoc; + uint32 button; + GetMouse(&tmpLoc, &button); + if (button) + message.AddInt32("buttons", (int32)button); + + if (button & B_PRIMARY_MOUSE_BUTTON) { + // add an action specifier to the message, so that it is not copied + message.AddInt32("be:actions", + (modifiers() & B_OPTION_KEY) != 0 ? B_COPY_TARGET : B_MOVE_TARGET); + } + + DragMessage(&message, dragBitmap, B_OP_ALPHA, + BPoint(fClickPoint.x + hIconOffset, fClickPoint.y), this); +} + + +void +DraggableContainerIcon::Draw(BRect /*updateRect*/) +{ + BContainerWindow *window = dynamic_cast(Window()); + if (window == NULL) + return; + + // Draw the icon, straddling the border + SetDrawingMode(B_OP_OVER); + float iconOffset = (Bounds().Width()-B_MINI_ICON)/2; + IconCache::sIconCache->Draw(window->TargetModel(), this, BPoint(iconOffset, iconOffset), + kNormalIcon, B_MINI_ICON, true); +} + + +// #pragma mark - + + +BContainerWindow::BContainerWindow(LockingList *list, + uint32 containerWindowFlags, + window_look look, window_feel feel, uint32 flags, uint32 workspace) + : BWindow(InitialWindowRect(feel), "TrackerWindow", look, feel, flags, + workspace), + fFileContextMenu(NULL), + fWindowContextMenu(NULL), + fDropContextMenu(NULL), + fVolumeContextMenu(NULL), + fDragContextMenu(NULL), + fMoveToItem(NULL), + fCopyToItem(NULL), + fCreateLinkItem(NULL), + fOpenWithItem(NULL), + fNavigationItem(NULL), + fMenuBar(NULL), + fNavigator(NULL), + fPoseView(NULL), + fWindowList(list), + fAttrMenu(NULL), + fWindowMenu(NULL), + fFileMenu(NULL), + fSelectionWindow(NULL), + fTaskLoop(NULL), + fIsTrash(false), + fInTrash(false), + fIsPrinters(false), + fContainerWindowFlags(containerWindowFlags), + fBackgroundImage(NULL), + fSavedZoomRect(0, 0, -1, -1), + fContextMenu(NULL), + fDragMessage(NULL), + fCachedTypesList(NULL), + fStateNeedsSaving(false), + fSaveStateIsEnabled(true), + fIsWatchingPath(false) +{ + InitIconPreloader(); + + if (list) { + ASSERT(list->IsLocked()); + list->AddItem(this); + } + + AddCommonFilter(new BMessageFilter(B_MOUSE_DOWN, ActivateWindowFilter)); + + Run(); + + // Watch out for settings changes: + if (TTracker *app = dynamic_cast(be_app)) { + app->Lock(); + app->StartWatching(this, kWindowsShowFullPathChanged); + app->StartWatching(this, kSingleWindowBrowseChanged); + app->StartWatching(this, kShowNavigatorChanged); + app->StartWatching(this, kDontMoveFilesToTrashChanged); + app->Unlock(); + } + + // ToDo: remove me once we have undo/redo menu items + // (that is, move them to AddShortcuts()) + AddShortcut('Z', B_COMMAND_KEY, new BMessage(B_UNDO), this); + AddShortcut('Z', B_COMMAND_KEY | B_SHIFT_KEY, new BMessage(kRedo), this); +} + + +BContainerWindow::~BContainerWindow() +{ + ASSERT(IsLocked()); + + // stop the watchers + if (TTracker *app = dynamic_cast(be_app)) { + app->Lock(); + app->StopWatching(this, kWindowsShowFullPathChanged); + app->StopWatching(this, kSingleWindowBrowseChanged); + app->StopWatching(this, kShowNavigatorChanged); + app->StopWatching(this, kDontMoveFilesToTrashChanged); + app->Unlock(); + } + + delete fTaskLoop; + delete fBackgroundImage; + delete fDragMessage; + delete fCachedTypesList; + + if (fSelectionWindow && fSelectionWindow->Lock()) + fSelectionWindow->Quit(); +} + + +BRect +BContainerWindow::InitialWindowRect(window_feel feel) +{ + if (feel != kPrivateDesktopWindowFeel) + return sNewWindRect; + + // do not offset desktop window + BRect result = sNewWindRect; + result.OffsetTo(0, 0); + return result; +} + + +void +BContainerWindow::Minimize(bool minimize) +{ + if (minimize && (modifiers() & B_OPTION_KEY) != 0) + do_minimize_team(BRect(0, 0, 0, 0), be_app->Team(), true); + else + _inherited::Minimize(minimize); +} + + +bool +BContainerWindow::QuitRequested() +{ + // this is a response to the DeskBar sending us a B_QUIT, when it really + // means to say close all your windows. It might be better to have it + // send a kCloseAllWindows message and have windowless apps stay running, + // which is what we will do for the Tracker + if (CurrentMessage() + && (CurrentMessage()->FindInt32("modifiers") & B_CONTROL_KEY)) + be_app->PostMessage(kCloseAllWindows); + + Hide(); + // this will close the window instantly, even if + // the file system is very busy right now + return true; +} + + +void +BContainerWindow::Quit() +{ + // get rid of context menus + if (fNavigationItem) { + BMenu *menu = fNavigationItem->Menu(); + if (menu) + menu->RemoveItem(fNavigationItem); + delete fNavigationItem; + fNavigationItem = NULL; + } + + if (fOpenWithItem && !fOpenWithItem->Menu()) { + delete fOpenWithItem; + fOpenWithItem = NULL; + } + + if (fMoveToItem && !fMoveToItem->Menu()) { + delete fMoveToItem; + fMoveToItem = NULL; + } + + if (fCopyToItem && !fCopyToItem->Menu()) { + delete fCopyToItem; + fCopyToItem = NULL; + } + + if (fCreateLinkItem && !fCreateLinkItem->Menu()) { + delete fCreateLinkItem; + fCreateLinkItem = NULL; + } + + if (fAttrMenu && !fAttrMenu->Supermenu()) { + delete fAttrMenu; + fAttrMenu = NULL; + } + + delete fFileContextMenu; + fFileContextMenu = NULL; + delete fWindowContextMenu; + fWindowContextMenu = NULL; + delete fDropContextMenu; + fDropContextMenu = NULL; + delete fVolumeContextMenu; + fVolumeContextMenu = NULL; + delete fDragContextMenu; + fDragContextMenu = NULL; + + int32 windowCount = 0; + + // This is a deadlock code sequence - need to change this + // to acquire the window list while this container window is unlocked + if (fWindowList) { + AutoLock > lock(fWindowList); + if (lock.IsLocked()) { + fWindowList->RemoveItem(this); + windowCount = fWindowList->CountItems(); + } + } + + if (StateNeedsSaving()) + SaveState(); + + if (fWindowList && windowCount == 0) + be_app->PostMessage(B_QUIT_REQUESTED); + + _inherited::Quit(); +} + + +BPoseView * +BContainerWindow::NewPoseView(Model *model, BRect rect, uint32 viewMode) +{ + return new BPoseView(model, rect, viewMode); +} + + +void +BContainerWindow::UpdateIfTrash(Model *model) +{ + BEntry entry(model->EntryRef()); + + if (entry.InitCheck() == B_OK) { + fIsTrash = FSIsTrashDir(&entry); + fInTrash = FSInTrashDir(model->EntryRef()); + fIsPrinters = FSIsPrintersDir(&entry); + } +} + + +void +BContainerWindow::CreatePoseView(Model *model) +{ + UpdateIfTrash(model); + BRect rect(Bounds()); + + TrackerSettings settings; + if (settings.SingleWindowBrowse() + && settings.ShowNavigator() + && model->IsDirectory()) + rect.top += BNavigator::CalcNavigatorHeight() + 1; + + rect.right -= B_V_SCROLL_BAR_WIDTH; + rect.bottom -= B_H_SCROLL_BAR_HEIGHT; + fPoseView = NewPoseView(model, rect, kListMode); + AddChild(fPoseView); + + if (settings.SingleWindowBrowse() + && model->IsDirectory() + && !fPoseView->IsFilePanel()) { + BRect rect(Bounds()); + rect.top = 0; + // The KeyMenuBar isn't attached yet, otherwise we'd use that to get the offset. + rect.bottom = BNavigator::CalcNavigatorHeight(); + fNavigator = new BNavigator(model, rect); + if (!settings.ShowNavigator()) + fNavigator->Hide(); + AddChild(fNavigator); + } + SetPathWatchingEnabled(settings.ShowNavigator() || settings.ShowFullPathInTitleBar()); +} + + +void +BContainerWindow::AddContextMenus() +{ + // create context sensitive menus + fFileContextMenu = new BPopUpMenu("FileContext", false, false); + fFileContextMenu->SetFont(be_plain_font); + AddFileContextMenus(fFileContextMenu); + + fVolumeContextMenu = new BPopUpMenu("VolumeContext", false, false); + fVolumeContextMenu->SetFont(be_plain_font); + AddVolumeContextMenus(fVolumeContextMenu); + + fWindowContextMenu = new BPopUpMenu("WindowContext", false, false); + fWindowContextMenu->SetFont(be_plain_font); + AddWindowContextMenus(fWindowContextMenu); + + fDropContextMenu = new BPopUpMenu("DropContext", false, false); + fDropContextMenu->SetFont(be_plain_font); + AddDropContextMenus(fDropContextMenu); + + fDragContextMenu = new BSlowContextMenu("DragContext"); + // will get added and built dynamically in ShowContextMenu +} + + +void +BContainerWindow::RepopulateMenus() +{ + // Avoid these menus to be destroyed: + if (fMoveToItem && fMoveToItem->Menu()) + fMoveToItem->Menu()->RemoveItem(fMoveToItem); + + if (fCopyToItem && fCopyToItem->Menu()) + fCopyToItem->Menu()->RemoveItem(fCopyToItem); + + if (fCreateLinkItem && fCreateLinkItem->Menu()) + fCreateLinkItem->Menu()->RemoveItem(fCreateLinkItem); + + if (fOpenWithItem && fOpenWithItem->Menu()) { + fOpenWithItem->Menu()->RemoveItem(fOpenWithItem); + delete fOpenWithItem; + fOpenWithItem = NULL; + } + + if (fNavigationItem) { + BMenu *menu = fNavigationItem->Menu(); + if (menu) { + menu->RemoveItem(fNavigationItem); + BMenuItem *item = menu->RemoveItem((int32)0); + ASSERT(item != fNavigationItem); + delete item; + } + } + + delete fFileContextMenu; + fFileContextMenu = new BPopUpMenu("FileContext", false, false); + fFileContextMenu->SetFont(be_plain_font); + AddFileContextMenus(fFileContextMenu); + + delete fWindowContextMenu; + fWindowContextMenu = new BPopUpMenu("WindowContext", false, false); + fWindowContextMenu->SetFont(be_plain_font); + AddWindowContextMenus(fWindowContextMenu); + + fMenuBar->RemoveItem(fFileMenu); + delete fFileMenu; + fFileMenu = new BMenu("File"); + AddFileMenu(fFileMenu); + fMenuBar->AddItem(fFileMenu); + + fMenuBar->RemoveItem(fWindowMenu); + delete fWindowMenu; + fWindowMenu = new BMenu("Window"); + fMenuBar->AddItem(fWindowMenu); + AddWindowMenu(fWindowMenu); + + // just create the attribute, decide to add it later + fMenuBar->RemoveItem(fAttrMenu); + delete fAttrMenu; + fAttrMenu = new BMenu("Attributes"); + NewAttributeMenu(fAttrMenu); + if (PoseView()->ViewMode() == kListMode) + ShowAttributeMenu(); + + int32 selectCount = PoseView()->SelectionList()->CountItems(); + + SetupOpenWithMenu(fFileMenu); + SetupMoveCopyMenus(selectCount + ? PoseView()->SelectionList()->FirstItem()->TargetModel()->EntryRef() : NULL, fFileMenu); +} + + +void +BContainerWindow::Init(const BMessage *message) +{ + float y_delta; + BEntry entry; + + ASSERT(fPoseView); + if (!fPoseView) + return; + + // deal with new unconfigured folders + if (NeedsDefaultStateSetup()) + SetUpDefaultState(); + + if (ShouldAddScrollBars()) + fPoseView->AddScrollBars(); + + fMoveToItem = new BMenuItem(new BNavMenu("Move to", kMoveSelectionTo, this)); + fCopyToItem = new BMenuItem(new BNavMenu("Copy to", kCopySelectionTo, this)); + fCreateLinkItem = new BMenuItem(new BNavMenu("Create Link", kCreateLink, this), + new BMessage(kCreateLink)); + + TrackerSettings settings; + + if (ShouldAddMenus()) { + // add menu bar, menus and resize poseview to fit + fMenuBar = new BMenuBar(BRect(0, 0, Bounds().Width() + 1, 1), "MenuBar"); + fMenuBar->SetBorder(B_BORDER_FRAME); + AddMenus(); + AddChild(fMenuBar); + + y_delta = KeyMenuBar()->Bounds().Height() + 1; + + float navigatorDelta = 0; + + if (Navigator() && settings.ShowNavigator()) { + Navigator()->MoveTo(BPoint(0, y_delta)); + navigatorDelta = BNavigator::CalcNavigatorHeight() + 1; + } + + fPoseView->MoveTo(BPoint(0, navigatorDelta + y_delta)); + fPoseView->ResizeBy(0, -(y_delta)); + if (fPoseView->VScrollBar()) { + fPoseView->VScrollBar()->MoveBy(0, KeyMenuBar()->Bounds().Height() + 1); + fPoseView->VScrollBar()->ResizeBy(0, -(KeyMenuBar()->Bounds().Height() + 1)); + } + + // add folder icon to menu bar + if (!TargetModel()->IsRoot() && !TargetModel()->IsVolume() && !IsTrash() && !IsPrintersDir()) { + float iconSize = fMenuBar->Bounds().Height() - 2; + if (iconSize < 16) + iconSize = 16; + float iconPosY = 1 + (fMenuBar->Bounds().Height() - 2 - iconSize) / 2; + BView *icon = new DraggableContainerIcon(BRect(Bounds().Width() - 4 - iconSize + 1, + iconPosY, Bounds().Width() - 4, iconPosY + iconSize - 1), + "ThisContainer", B_FOLLOW_RIGHT); + fMenuBar->AddChild(icon); + } + } else + // add equivalents of the menu shortcuts to the menuless desktop window + AddShortcuts(); + + AddContextMenus(); + AddShortcut('T', B_COMMAND_KEY | B_SHIFT_KEY, new BMessage(kDelete), PoseView()); + AddShortcut('K', B_COMMAND_KEY | B_SHIFT_KEY, new BMessage(kCleanupAll), + PoseView()); + AddShortcut('Q', B_COMMAND_KEY | B_OPTION_KEY | B_SHIFT_KEY | B_CONTROL_KEY, + new BMessage(kQuitTracker)); + + AddShortcut(B_DOWN_ARROW, B_COMMAND_KEY, new BMessage(kOpenSelection), + PoseView()); + + SetSingleWindowBrowseShortcuts(settings.SingleWindowBrowse()); + +#if DEBUG + // add some debugging shortcuts + AddShortcut('D', B_COMMAND_KEY | B_CONTROL_KEY, new BMessage('dbug'), PoseView()); + AddShortcut('C', B_COMMAND_KEY | B_CONTROL_KEY, new BMessage('dpcc'), PoseView()); + AddShortcut('F', B_COMMAND_KEY | B_CONTROL_KEY, new BMessage('dpfl'), PoseView()); + AddShortcut('F', B_COMMAND_KEY | B_CONTROL_KEY | B_OPTION_KEY, + new BMessage('dpfL'), PoseView()); +#endif + + if (message) + RestoreState(*message); + else + RestoreState(); + + if (ShouldAddMenus() && PoseView()->ViewMode() == kListMode) { + // for now only show attributes in list view + // eventually enable attribute menu to allow users to select + // using different attributes as titles in icon view modes + ShowAttributeMenu(); + } + MarkAttributeMenu(fAttrMenu); + CheckScreenIntersect(); + + if (fBackgroundImage && !dynamic_cast(this) + && PoseView()->ViewMode() != kListMode) + fBackgroundImage->Show(PoseView(), current_workspace()); + + Show(); + + // done showing, turn the B_NO_WORKSPACE_ACTIVATION flag off; + // it was on to prevent workspace jerking during boot + SetFlags(Flags() & ~B_NO_WORKSPACE_ACTIVATION); +} + + +void +BContainerWindow::RestoreState() +{ + SetSizeLimits(kContainerWidthMinLimit, 10000, kContainerWindowHeightLimit, 10000); + + UpdateTitle(); + + WindowStateNodeOpener opener(this, false); + RestoreWindowState(opener.StreamNode()); + fPoseView->Init(opener.StreamNode()); + + RestoreStateCommon(); +} + + +void +BContainerWindow::RestoreState(const BMessage &message) +{ + SetSizeLimits(kContainerWidthMinLimit, 10000, kContainerWindowHeightLimit, 10000); + + UpdateTitle(); + + RestoreWindowState(message); + fPoseView->Init(message); + + RestoreStateCommon(); +} + + +void +BContainerWindow::RestoreStateCommon() +{ + if (BootedInSafeMode()) + // don't pick up backgrounds in safe mode + return; + + WindowStateNodeOpener opener(this, false); + + bool isDesktop = dynamic_cast(this); + if (!TargetModel()->IsRoot() && opener.Node()) + // don't pick up background image for root disks + // to do this, would have to have a unique attribute for the + // disks window that doesn't collide with the desktop + // for R4 this was not done to make things simpler + // the default image will still work though + fBackgroundImage = BackgroundImage::GetBackgroundImage( + opener.Node(), isDesktop); + // look for background image info in the window's node + + BNode defaultingNode; + if (!fBackgroundImage && !isDesktop + && DefaultStateSourceNode(kDefaultFolderTemplate, &defaultingNode)) + // look for background image info in the source for defaults + fBackgroundImage = BackgroundImage::GetBackgroundImage(&defaultingNode, isDesktop); +} + + +void +BContainerWindow::UpdateTitle() +{ + // set title to full path, if necessary + if (TrackerSettings().ShowFullPathInTitleBar()) { + // use the Entry's full path + BPath path; + TargetModel()->GetPath(&path); + SetTitle(path.Path()); + } else + // use the default look + SetTitle(TargetModel()->Name()); + + if (Navigator()) + Navigator()->UpdateLocation(PoseView()->TargetModel(), kActionUpdatePath); +} + + +void +BContainerWindow::UpdateBackgroundImage() +{ + if (BootedInSafeMode()) + return; + + bool isDesktop = dynamic_cast(this) != NULL; + WindowStateNodeOpener opener(this, false); + + if (!TargetModel()->IsRoot() && opener.Node()) + fBackgroundImage = BackgroundImage::Refresh(fBackgroundImage, + opener.Node(), isDesktop, PoseView()); + + // look for background image info in the window's node + BNode defaultingNode; + if (!fBackgroundImage && !isDesktop + && DefaultStateSourceNode(kDefaultFolderTemplate, &defaultingNode)) + // look for background image info in the source for defaults + fBackgroundImage = BackgroundImage::Refresh(fBackgroundImage, + &defaultingNode, isDesktop, PoseView()); +} + + +void +BContainerWindow::FrameResized(float, float) +{ + if (PoseView()) { + PoseView()->UpdateScrollRange(); + PoseView()->ResetPosePlacementHint(); + } + + fStateNeedsSaving = true; +} + + +void +BContainerWindow::FrameMoved(BPoint) +{ + fStateNeedsSaving = true; +} + + +void +BContainerWindow::WorkspacesChanged(uint32, uint32) +{ + fStateNeedsSaving = true; +} + + +void +BContainerWindow::ViewModeChanged(uint32 oldMode, uint32 newMode) +{ + if (!fBackgroundImage) + return; + + if (newMode == kListMode) + fBackgroundImage->Remove(); + else if (oldMode == kListMode) + fBackgroundImage->Show(PoseView(), current_workspace()); +} + + +void +BContainerWindow::CheckScreenIntersect() +{ + BScreen screen(this); + BRect screenFrame(screen.Frame()); + BRect frame(Frame()); + + if (sNewWindRect.bottom > screenFrame.bottom) + sNewWindRect.OffsetTo(85, 50); + + if (sNewWindRect.right > screenFrame.right) + sNewWindRect.OffsetTo(85, 50); + + if (!frame.Intersects(screenFrame)) + MoveTo(sNewWindRect.LeftTop()); +} + + +void +BContainerWindow::SaveState(bool hide) +{ + if (SaveStateIsEnabled()) { + WindowStateNodeOpener opener(this, true); + if (opener.StreamNode()) + SaveWindowState(opener.StreamNode()); + if (hide) + Hide(); + if (opener.StreamNode()) + fPoseView->SaveState(opener.StreamNode()); + + fStateNeedsSaving = false; + } +} + + +void +BContainerWindow::SaveState(BMessage &message) const +{ + if (SaveStateIsEnabled()) { + SaveWindowState(message); + fPoseView->SaveState(message); + } +} + + +bool +BContainerWindow::StateNeedsSaving() const +{ + return fStateNeedsSaving || PoseView()->StateNeedsSaving(); +} + + +status_t +BContainerWindow::GetLayoutState(BNode *node, BMessage *message) +{ + // ToDo: + // get rid of this, use AttrStream instead + status_t result = node->InitCheck(); + if (result != B_OK) + return result; + + node->RewindAttrs(); + char attrName[256]; + while (node->GetNextAttrName(attrName) == B_OK) { + attr_info info; + node->GetAttrInfo(attrName, &info); + + // filter out attributes that are not related to window position + // and column resizing + // more can be added as needed + if (strcmp(attrName, kAttrWindowFrame) != 0 + && strcmp(attrName, kAttrColumns) != 0 + && strcmp(attrName, kAttrViewState) != 0 + && strcmp(attrName, kAttrColumnsForeign) != 0 + && strcmp(attrName, kAttrViewStateForeign) != 0) + continue; + + char *buffer = new char[info.size]; + if (node->ReadAttr(attrName, info.type, 0, buffer, (size_t)info.size) == info.size) + message->AddData(attrName, info.type, buffer, (ssize_t)info.size); + delete [] buffer; + } + return B_OK; +} + + +status_t +BContainerWindow::SetLayoutState(BNode *node, const BMessage *message) +{ + status_t result = node->InitCheck(); + if (result != B_OK) + return result; + + for (int32 globalIndex = 0; ;) { +#if B_BEOS_VERSION_DANO + const char *name; +#else + char *name; +#endif + type_code type; + int32 count; + status_t result = message->GetInfo(B_ANY_TYPE, globalIndex, &name, + &type, &count); + if (result != B_OK) + break; + + for (int32 index = 0; index < count; index++) { + const void *buffer; + int32 size; + result = message->FindData(name, type, index, &buffer, &size); + if (result != B_OK) { + PRINT(("error reading %s \n", name)); + return result; + } + + if (node->WriteAttr(name, type, 0, buffer, (size_t)size) != size) { + PRINT(("error writing %s \n", name)); + return result; + } + globalIndex++; + } + } + return B_OK; +} + + +bool +BContainerWindow::ShouldAddMenus() const +{ + return true; +} + + +bool +BContainerWindow::ShouldAddScrollBars() const +{ + return true; +} + + +bool +BContainerWindow::ShouldAddCountView() const +{ + return true; +} + + +Model * +BContainerWindow::TargetModel() const +{ + return fPoseView->TargetModel(); +} + + +void +BContainerWindow::SelectionChanged() +{ +} + + +void +BContainerWindow::Zoom(BPoint, float, float) +{ + BRect oldZoomRect(fSavedZoomRect); + fSavedZoomRect = Frame(); + ResizeToFit(); + + if (fSavedZoomRect == Frame()) + if (oldZoomRect.IsValid()) + ResizeTo(oldZoomRect.Width(), oldZoomRect.Height()); +} + + +void +BContainerWindow::ResizeToFit() +{ + BScreen screen(this); + BRect screenFrame(screen.Frame()); + + screenFrame.InsetBy(5, 5); + screenFrame.top += 15; // keeps title bar of window visible + + BRect frame(Frame()); + + // move frame left top on screen + BPoint leftTop(frame.LeftTop()); + leftTop.ConstrainTo(screenFrame); + frame.OffsetTo(leftTop); + + // resize to extent size + float menuHeight; + if (KeyMenuBar()) + menuHeight = KeyMenuBar()->Bounds().Height(); + else + menuHeight = 0; + + BRect extent(PoseView()->Extent()); + frame.right = frame.left + extent.Width() + (float)B_V_SCROLL_BAR_WIDTH + 1.0f; + frame.bottom = frame.top + extent.Height() + (float)B_H_SCROLL_BAR_HEIGHT + 1.0f + + menuHeight; + + if (PoseView()->ViewMode() == kListMode) + frame.bottom += kTitleViewHeight + 1; // account for list titles + + TrackerSettings settings; + if (settings.SingleWindowBrowse() + && settings.ShowNavigator() + && Navigator()) + frame.bottom += fNavigator->Bounds().bottom + 1; + + // ToDo: + // clean this up, move each special case to respective class + + if (!TargetModel()) + frame.bottom += 60; // Open with window + else if (TargetModel()->IsQuery()) + frame.bottom += 15; // account for query string + + // make sure entire window fits on screen + frame = frame & screenFrame; + + ResizeTo(frame.Width(), frame.Height()); + MoveTo(frame.LeftTop()); + PoseView()->DisableScrollBars(); + PoseView()->ScrollTo(extent.LeftTop()); + PoseView()->UpdateScrollRange(); + PoseView()->EnableScrollBars(); +} + + +void +BContainerWindow::MessageReceived(BMessage *message) +{ + switch (message->what) { + case B_CUT: + case B_COPY: + case B_PASTE: + case kCutMoreSelectionToClipboard: + case kCopyMoreSelectionToClipboard: + case kPasteLinksFromClipboard: + { + BView *view = CurrentFocus(); + if (view->LockLooper()) { + view->MessageReceived(message); + view->UnlockLooper(); + } + break; + } + + case kNewFolder: + PostMessage(message, PoseView()); + break; + + case kContextMenuDragNDrop: + // + // sent when the SlowContextPopup goes away + // + if (fWaitingForRefs && Dragging()) + PostMessage(message, PoseView()); + else + fWaitingForRefs = false; + break; + + case kRestoreState: + if (message->HasMessage("state")) { + BMessage state; + message->FindMessage("state", &state); + Init(&state); + } else + Init(); + break; + + case kResizeToFit: + ResizeToFit(); + break; + + case kLoadAddOn: + LoadAddOn(message); + break; + + case kCopySelectionTo: + { + entry_ref ref; + if (message->FindRef("refs", &ref) != B_OK) + break; + + BRoster().AddToRecentFolders(&ref); + + Model model(&ref); + if (model.InitCheck() != B_OK) + break; + + if (*model.NodeRef() == *TargetModel()->NodeRef()) + PoseView()->DuplicateSelection(); + else + PoseView()->MoveSelectionInto(&model, this, true); + + break; + } + case kMoveSelectionTo: + { + entry_ref ref; + if (message->FindRef("refs", &ref) != B_OK) + break; + + BRoster().AddToRecentFolders(&ref); + + Model model(&ref); + if (model.InitCheck() != B_OK) + break; + + PoseView()->MoveSelectionInto(&model, this, false); + break; + } + + case kCreateLink: + case kCreateRelativeLink: + { + entry_ref ref; + if (message->FindRef("refs", &ref) == B_OK) { + BRoster().AddToRecentFolders(&ref); + + Model model(&ref); + if (model.InitCheck() != B_OK) + break; + + PoseView()->MoveSelectionInto(&model, this, false, true, + message->what == kCreateRelativeLink); + } else { + // no destination specified, create link in same dir as item + if (!TargetModel()->IsQuery()) + PoseView()->MoveSelectionInto(TargetModel(), this, false, true, + (message->what == kCreateRelativeLink)); + } + break; + } + + case kShowSelectionWindow: + ShowSelectionWindow(); + break; + + case kSelectMatchingEntries: + PoseView()->SelectMatchingEntries(message); + break; + + case kFindButton: + (new FindWindow())->Show(); + break; + + case kQuitTracker: + be_app->PostMessage(B_QUIT_REQUESTED); + break; + + case kRestoreBackgroundImage: + UpdateBackgroundImage(); + break; + + case kSwitchDirectory: + { + entry_ref ref; + if (message->FindRef("refs", &ref) == B_OK) { + BEntry entry; + if (entry.SetTo(&ref) == B_OK) { + if (StateNeedsSaving()) + SaveState(false); + + bool wasInTrash = IsTrash() || InTrash(); + bool isRoot = PoseView()->TargetModel()->IsRoot(); + + // Switch dir and apply new state + WindowStateNodeOpener opener(this, false); + opener.SetTo(&entry, false); + + // Update PoseView + PoseView()->SwitchDir(&ref, opener.StreamNode()); + + fIsTrash = FSIsTrashDir(&entry); + fInTrash = FSInTrashDir(&ref); + + if (wasInTrash ^ (IsTrash() || InTrash()) + || isRoot != PoseView()->TargetModel()->IsRoot()) + RepopulateMenus(); + + // Update Navigation bar + if (Navigator()) { + int32 action = kActionSet; + if (message->FindInt32("action", &action) != B_OK) + // Design problem? Why does FindInt32 touch + // 'action' at all if he can't find it?? + action = kActionSet; + + Navigator()->UpdateLocation(PoseView()->TargetModel(), action); + } + + TrackerSettings settings; + if (settings.ShowNavigator() || settings.ShowFullPathInTitleBar()) + SetPathWatchingEnabled(true); + + // Update window title + UpdateTitle(); + } + } + break; + } + + case B_REFS_RECEIVED: + if (Dragging()) { + // + // ref in this message is the target, + // the end point of the drag + // + entry_ref ref; + if (message->FindRef("refs", &ref) == B_OK) { + //printf("BContainerWindow::MessageReceived - refs received\n"); + fWaitingForRefs = false; + BEntry entry(&ref, true); + // + // don't copy to printers dir + if (!FSIsPrintersDir(&entry)) { + if (entry.InitCheck() == B_OK && entry.IsDirectory()) { + // + // build of list of entry_refs from the list + // in the drag message + entry_ref dragref; + int32 index = 0; + BObjectList *list = new BObjectList(0, true); + while (fDragMessage->FindRef("refs", index++, &dragref) == B_OK) + list->AddItem(new entry_ref(dragref)); + // + // compare the target and one of the drag items' parent + // + BEntry item(&dragref, true); + BEntry itemparent; + item.GetParent(&itemparent); + entry_ref parentref; + itemparent.GetRef(&parentref); + + entry_ref targetref; + entry.GetRef(&targetref); + + // if they don't match, move/copy + if (targetref != parentref) + // copy drag contents to target ref in message + FSMoveToFolder(list, new BEntry(entry), kMoveSelectionTo); + + } else { + // current message sent to apps is only B_REFS_RECEIVED + fDragMessage->what = B_REFS_RECEIVED; + FSLaunchItem(&ref, (const BMessage *)fDragMessage, true, true); + } + } + } + DragStop(); + } + break; + + case B_OBSERVER_NOTICE_CHANGE: + { + int32 observerWhat; + if (message->FindInt32("be:observe_change_what", &observerWhat) == B_OK) { + TrackerSettings settings; + switch (observerWhat) { + case kWindowsShowFullPathChanged: + UpdateTitle(); + if (!IsPathWatchingEnabled() && settings.ShowFullPathInTitleBar()) + SetPathWatchingEnabled(true); + if (IsPathWatchingEnabled() && !(settings.ShowNavigator() || settings.ShowFullPathInTitleBar())) + SetPathWatchingEnabled(false); + break; + + case kSingleWindowBrowseChanged: + if (settings.SingleWindowBrowse() + && !Navigator() + && TargetModel()->IsDirectory() + && !PoseView()->IsFilePanel() + && !PoseView()->IsDesktopWindow()) { + BRect rect(Bounds()); + rect.top = KeyMenuBar()->Bounds().Height() + 1; + rect.bottom = rect.top + BNavigator::CalcNavigatorHeight(); + fNavigator = new BNavigator(TargetModel(), rect); + fNavigator->Hide(); + AddChild(fNavigator); + SetPathWatchingEnabled(settings.ShowNavigator() || settings.ShowFullPathInTitleBar()); + } + SetSingleWindowBrowseShortcuts(settings.SingleWindowBrowse()); + break; + + case kShowNavigatorChanged: + ShowNavigator(settings.ShowNavigator()); + if (!IsPathWatchingEnabled() && settings.ShowNavigator()) + SetPathWatchingEnabled(true); + if (IsPathWatchingEnabled() && !(settings.ShowNavigator() || settings.ShowFullPathInTitleBar())) + SetPathWatchingEnabled(false); + break; + + case kDontMoveFilesToTrashChanged: + { + bool dontMoveToTrash = settings.DontMoveFilesToTrash(); + + BMenuItem *item = fFileContextMenu->FindItem(kMoveToTrash); + if (item) + item->SetLabel(dontMoveToTrash ? "Delete" : "Move To Trash"); + + // Deskbar doesn't have a menu bar, so check if there is fMenuBar + if (fMenuBar && fFileMenu) { + item = fFileMenu->FindItem(kMoveToTrash); + if (item) + item->SetLabel(dontMoveToTrash ? "Delete" : "Move To Trash"); + } + UpdateIfNeeded(); + } + break; + + default: + _inherited::MessageReceived(message); + } + } + break; + } + + case B_NODE_MONITOR: + UpdateTitle(); + break; + + case B_UNDO: + FSUndo(); + break; + + //case B_REDO: /* only defined in Dano/Zeta/OpenBeOS */ + case kRedo: + FSRedo(); + break; + + default: + _inherited::MessageReceived(message); + } +} + + +void +BContainerWindow::SetCutItem(BMenu *menu) +{ + BMenuItem *item; + if ((item = menu->FindItem(B_CUT)) == NULL + && (item = menu->FindItem(kCutMoreSelectionToClipboard)) == NULL) + return; + + item->SetEnabled(PoseView()->SelectionList()->CountItems() > 0 + || PoseView() != CurrentFocus()); + + if (modifiers() & B_SHIFT_KEY) { + item->SetLabel("Cut more"); + item->SetShortcut('X', B_COMMAND_KEY | B_SHIFT_KEY); + item->SetMessage(new BMessage(kCutMoreSelectionToClipboard)); + } else { + item->SetLabel("Cut"); + item->SetShortcut('X', B_COMMAND_KEY); + item->SetMessage(new BMessage(B_CUT)); + } +} + + +void +BContainerWindow::SetCopyItem(BMenu *menu) +{ + BMenuItem *item; + if ((item = menu->FindItem(B_COPY)) == NULL + && (item = menu->FindItem(kCopyMoreSelectionToClipboard)) == NULL) + return; + + item->SetEnabled(PoseView()->SelectionList()->CountItems() > 0 + || PoseView() != CurrentFocus()); + + if (modifiers() & B_SHIFT_KEY) { + item->SetLabel("Copy more"); + item->SetShortcut('C', B_COMMAND_KEY | B_SHIFT_KEY); + item->SetMessage(new BMessage(kCopyMoreSelectionToClipboard)); + } else { + item->SetLabel("Copy"); + item->SetShortcut('C', B_COMMAND_KEY); + item->SetMessage(new BMessage(B_COPY)); + } +} + + +void +BContainerWindow::SetPasteItem(BMenu *menu) +{ + BMenuItem *item; + if ((item = menu->FindItem(B_PASTE)) == NULL + && (item = menu->FindItem(kPasteLinksFromClipboard)) == NULL) + return; + + item->SetEnabled(FSClipboardHasRefs() || PoseView() != CurrentFocus()); + + if (modifiers() & B_SHIFT_KEY) { + item->SetLabel("Paste links"); + item->SetShortcut('V', B_COMMAND_KEY | B_SHIFT_KEY); + item->SetMessage(new BMessage(kPasteLinksFromClipboard)); + } else { + item->SetLabel("Paste"); + item->SetShortcut('V', B_COMMAND_KEY); + item->SetMessage(new BMessage(B_PASTE)); + } +} + + +void +BContainerWindow::SetCleanUpItem(BMenu *menu) +{ + BMenuItem *item; + if ((item = menu->FindItem(kCleanup)) == NULL + && (item = menu->FindItem(kCleanupAll)) == NULL) + return; + + item->SetEnabled(PoseView()->CountItems() > 0 + && (PoseView()->ViewMode() != kListMode)); + + if (modifiers() & B_SHIFT_KEY) { + item->SetLabel("Clean Up All"); + item->SetShortcut('K', B_COMMAND_KEY | B_SHIFT_KEY); + item->SetMessage(new BMessage(kCleanupAll)); + } else { + item->SetLabel("Clean Up"); + item->SetShortcut('K', B_COMMAND_KEY); + item->SetMessage(new BMessage(kCleanup)); + } +} + + +void +BContainerWindow::SetCloseItem(BMenu *menu) +{ + BMenuItem *item; + if ((item = menu->FindItem(B_QUIT_REQUESTED)) == NULL + && (item = menu->FindItem(kCloseAllWindows)) == NULL) + return; + + if (modifiers() & B_OPTION_KEY) { + item->SetLabel("Close All"); + item->SetShortcut('W', B_COMMAND_KEY | B_OPTION_KEY); + item->SetTarget(be_app); + item->SetMessage(new BMessage(kCloseAllWindows)); + } else { + item->SetLabel("Close"); + item->SetShortcut('W', B_COMMAND_KEY); + item->SetTarget(this); + item->SetMessage(new BMessage(B_QUIT_REQUESTED)); + } +} + + +bool +BContainerWindow::IsShowing(const node_ref *node) const +{ + return PoseView()->Represents(node); +} + + +bool +BContainerWindow::IsShowing(const entry_ref *entry) const +{ + return PoseView()->Represents(entry); +} + + +void +BContainerWindow::AddMenus() +{ + fFileMenu = new BMenu("File"); + AddFileMenu(fFileMenu); + fMenuBar->AddItem(fFileMenu); + fWindowMenu = new BMenu("Window"); + fMenuBar->AddItem(fWindowMenu); + AddWindowMenu(fWindowMenu); + // just create the attribute, decide to add it later + fAttrMenu = new BMenu("Attributes"); + NewAttributeMenu(fAttrMenu); +} + + +void +BContainerWindow::AddFileMenu(BMenu *menu) +{ + if (!PoseView()->IsFilePanel()) + menu->AddItem(new BMenuItem("Find"B_UTF8_ELLIPSIS, + new BMessage(kFindButton), 'F')); + + if (!TargetModel()->IsQuery() && !IsTrash() && !IsPrintersDir()) { + if (!PoseView()->IsFilePanel()) { + TemplatesMenu *templateMenu = new TemplatesMenu(PoseView()); + menu->AddItem(templateMenu); + templateMenu->SetTargetForItems(PoseView()); + } else + menu->AddItem(new BMenuItem("New Folder", new BMessage(kNewFolder), 'N')); + } + menu->AddSeparatorItem(); + + menu->AddItem(new BMenuItem("Open", new BMessage(kOpenSelection), 'O')); + menu->AddItem(new BMenuItem("Get Info", new BMessage(kGetInfo), 'I')); + menu->AddItem(new BMenuItem("Edit Name", new BMessage(kEditItem), 'E')); + + if (IsTrash() || InTrash()) { + menu->AddItem(new BMenuItem("Delete", new BMessage(kDelete))); + menu->AddItem(new BMenuItem("Restore", new BMessage(kRestoreFromTrash))); + if (IsTrash()) { + // add as first item in menu + menu->AddItem(new BMenuItem("Empty Trash", new BMessage(kEmptyTrash)), 0); + menu->AddItem(new BSeparatorItem(), 1); + } + } else if (IsPrintersDir()) { + menu->AddItem(new BMenuItem("Add Printer"B_UTF8_ELLIPSIS, + new BMessage(kAddPrinter), 'N'), 0); + menu->AddItem(new BSeparatorItem(), 1); + menu->AddItem(new BMenuItem("Make Active Printer", + new BMessage(kMakeActivePrinter))); + } else { + menu->AddItem(new BMenuItem("Duplicate",new BMessage(kDuplicateSelection), 'D')); + + menu->AddItem(new BMenuItem(TrackerSettings().DontMoveFilesToTrash() ? + "Delete" : "Move to Trash", + new BMessage(kMoveToTrash), 'T')); + + menu->AddSeparatorItem(); + + // The "Move To", "Copy To", "Create Link" menus are inserted + // at this place, have a look at: + // BContainerWindow::SetupMoveCopyMenus() + } + + BMenuItem *cutItem = NULL, *copyItem = NULL, *pasteItem = NULL; + if (!IsPrintersDir()) { + menu->AddSeparatorItem(); + + menu->AddItem(cutItem = new BMenuItem("Cut", new BMessage(B_CUT), 'X')); + menu->AddItem(copyItem = new BMenuItem("Copy", new BMessage(B_COPY), 'C')); + menu->AddItem(pasteItem = new BMenuItem("Paste", new BMessage(B_PASTE), 'V')); + + menu->AddSeparatorItem(); + + menu->AddItem(new BMenuItem("Identify", new BMessage(kIdentifyEntry))); + BMenu *addOnMenuItem = new BMenu(kAddOnsMenuName); + addOnMenuItem->SetFont(be_plain_font); + menu->AddItem(addOnMenuItem); + } + + menu->SetTargetForItems(PoseView()); + if (cutItem) + cutItem->SetTarget(this); + if (copyItem) + copyItem->SetTarget(this); + if (pasteItem) + pasteItem->SetTarget(this); +} + + +void +BContainerWindow::AddWindowMenu(BMenu *menu) +{ + BMenuItem *item; + + item = new BMenuItem("Icon View", new BMessage(kIconMode), '1'); + item->SetTarget(PoseView()); + menu->AddItem(item); + + item = new BMenuItem("Mini Icon View", new BMessage(kMiniIconMode), '2'); + item->SetTarget(PoseView()); + menu->AddItem(item); + + item = new BMenuItem("List View", new BMessage(kListMode), '3'); + item->SetTarget(PoseView()); + menu->AddItem(item); + + menu->AddSeparatorItem(); + + item = new BMenuItem("Resize to Fit", new BMessage(kResizeToFit), 'Y'); + item->SetTarget(this); + menu->AddItem(item); + + item = new BMenuItem("Clean Up", new BMessage(kCleanup), 'K'); + item->SetTarget(PoseView()); + menu->AddItem(item); + + item = new BMenuItem("Select"B_UTF8_ELLIPSIS, new BMessage(kShowSelectionWindow), + 'A', B_SHIFT_KEY); + item->SetTarget(PoseView()); + menu->AddItem(item); + + item = new BMenuItem("Select All", new BMessage(B_SELECT_ALL), 'A'); + item->SetTarget(PoseView()); + menu->AddItem(item); + + item = new BMenuItem("Invert Selection", new BMessage(kInvertSelection), 'S'); + item->SetTarget(PoseView()); + menu->AddItem(item); + + if (!IsTrash()) { + item = new BMenuItem("Open Parent", new BMessage(kOpenParentDir), + B_UP_ARROW); + item->SetTarget(PoseView()); + menu->AddItem(item); + } + + item = new BMenuItem("Close", new BMessage(B_QUIT_REQUESTED), 'W'); + item->SetTarget(this); + menu->AddItem(item); + + menu->AddSeparatorItem(); + + item = new BMenuItem("Settings"B_UTF8_ELLIPSIS, new BMessage(kShowSettingsWindow)); + item->SetTarget(be_app); + menu->AddItem(item); +} + + +void +BContainerWindow::AddShortcuts() +{ + // add equivalents of the menu shortcuts to the menuless desktop window + ASSERT(!IsTrash()); + ASSERT(!PoseView()->IsFilePanel()); + ASSERT(!TargetModel()->IsQuery()); + + AddShortcut('X', B_COMMAND_KEY | B_SHIFT_KEY, new BMessage(kCutMoreSelectionToClipboard), this); + AddShortcut('C', B_COMMAND_KEY | B_SHIFT_KEY, new BMessage(kCopyMoreSelectionToClipboard), this); + AddShortcut('F', B_COMMAND_KEY, new BMessage(kFindButton), PoseView()); + AddShortcut('N', B_COMMAND_KEY, new BMessage(kNewFolder), PoseView()); + AddShortcut('O', B_COMMAND_KEY, new BMessage(kOpenSelection), PoseView()); + AddShortcut('I', B_COMMAND_KEY, new BMessage(kGetInfo), PoseView()); + AddShortcut('E', B_COMMAND_KEY, new BMessage(kEditItem), PoseView()); + AddShortcut('D', B_COMMAND_KEY, new BMessage(kDuplicateSelection), PoseView()); + AddShortcut('T', B_COMMAND_KEY, new BMessage(kMoveToTrash), PoseView()); + AddShortcut('K', B_COMMAND_KEY, new BMessage(kCleanup), PoseView()); + AddShortcut('A', B_COMMAND_KEY, new BMessage(B_SELECT_ALL), PoseView()); + AddShortcut('S', B_COMMAND_KEY, new BMessage(kInvertSelection), PoseView()); + AddShortcut('A', B_COMMAND_KEY | B_SHIFT_KEY, new BMessage(kShowSelectionWindow), PoseView()); + AddShortcut('G', B_COMMAND_KEY, new BMessage(kEditQuery), PoseView()); + // it is ok to add a global Edit query shortcut here, PoseView will + // filter out cases where selected pose is not a query + AddShortcut('U', B_COMMAND_KEY, new BMessage(kUnmountVolume), PoseView()); + AddShortcut(B_UP_ARROW, B_COMMAND_KEY, new BMessage(kOpenParentDir), PoseView()); + AddShortcut('O', B_COMMAND_KEY | B_CONTROL_KEY, new BMessage(kOpenSelectionWith), + PoseView()); +} + + +void +BContainerWindow::MenusBeginning() +{ + if (!fMenuBar) + return; + + if (CurrentMessage() && CurrentMessage()->what == B_MOUSE_DOWN) + // don't commit active pose if only a keyboard shortcut is + // invoked - this would prevent Cut/Copy/Paste from working + fPoseView->CommitActivePose(); + + // File menu + int32 selectCount = PoseView()->SelectionList()->CountItems(); + + SetupOpenWithMenu(fFileMenu); + SetupMoveCopyMenus(selectCount + ? PoseView()->SelectionList()->FirstItem()->TargetModel()->EntryRef() : NULL, fFileMenu); + + UpdateMenu(fMenuBar, kMenuBarContext); + + AddMimeTypesToMenu(fAttrMenu); + + if (IsPrintersDir()) + EnableNamedMenuItem(fFileMenu, "Make Active Printer", selectCount == 1); +} + + +void +BContainerWindow::MenusEnded() +{ + // when we're done we want to clear nav menus for next time + DeleteSubmenu(fNavigationItem); + DeleteSubmenu(fMoveToItem); + DeleteSubmenu(fCopyToItem); + DeleteSubmenu(fCreateLinkItem); + DeleteSubmenu(fOpenWithItem); +} + + +void +BContainerWindow::SetupNavigationMenu(const entry_ref *ref, BMenu *parent) +{ + // start by removing nav item (and separator) from old menu + if (fNavigationItem) { + BMenu *menu = fNavigationItem->Menu(); + if (menu) { + menu->RemoveItem(fNavigationItem); + BMenuItem *item = menu->RemoveItem((int32)0); + ASSERT(item != fNavigationItem); + delete item; + } + } + + // if we weren't passed a ref then we're navigating this window + if (!ref) + ref = TargetModel()->EntryRef(); + + BEntry entry; + if (entry.SetTo(ref) != B_OK) + return; + + // only navigate directories and queries (check for symlink here) + Model model(&entry); + entry_ref resolvedRef; + + if (model.InitCheck() != B_OK + || (!model.IsContainer() && !model.IsSymLink())) + return; + + if (model.IsSymLink()) { + if (entry.SetTo(model.EntryRef(), true) != B_OK) + return; + + Model resolvedModel(&entry); + if (resolvedModel.InitCheck() != B_OK || !resolvedModel.IsContainer()) + return; + + entry.GetRef(&resolvedRef); + ref = &resolvedRef; + } + + if (!fNavigationItem) { + fNavigationItem = new ModelMenuItem(&model, + new BNavMenu(model.Name(), B_REFS_RECEIVED, be_app, this)); + } + + // setup a navigation menu item which will dynamically load items + // as menu items are traversed + BNavMenu *navMenu = dynamic_cast(fNavigationItem->Submenu()); + navMenu->SetNavDir(ref); + fNavigationItem->SetLabel(model.Name()); + fNavigationItem->SetEntry(&entry); + + parent->AddItem(fNavigationItem, 0); + parent->AddItem(new BSeparatorItem(), 1); + + BMessage *message = new BMessage(B_REFS_RECEIVED); + message->AddRef("refs", ref); + fNavigationItem->SetMessage(message); + fNavigationItem->SetTarget(be_app); + + if (!Dragging()) + parent->SetTrackingHook(NULL, NULL); +} + + +void +BContainerWindow::SetUpEditQueryItem(BMenu *menu) +{ + ASSERT(menu); + // File menu + int32 selectCount = PoseView()->SelectionList()->CountItems(); + + // add Edit query if appropriate + bool queryInSelection = false; + if (selectCount && selectCount < 100) { + // only do this for a limited number of selected poses + + // if any queries selected, add an edit query menu item + for (int32 index = 0; index < selectCount; index++) { + BPose *pose = PoseView()->SelectionList()->ItemAt(index); + Model model(pose->TargetModel()->EntryRef(), true); + if (model.InitCheck() != B_OK) + continue; + + if (model.IsQuery() || model.IsQueryTemplate()) { + queryInSelection = true; + break; + } + } + } + + bool poseViewIsQuery = TargetModel()->IsQuery(); + // if the view is a query pose view, add edit query menu item + + BMenuItem *item = menu->FindItem("Edit Query"); + if (!poseViewIsQuery && !queryInSelection && item) + item->Menu()->RemoveItem(item); + + else if ((poseViewIsQuery || queryInSelection) && menu && !item) { + + // add edit query item after Open + item = menu->FindItem(kOpenSelection); + if (item) { + int32 itemIndex = item->Menu()->IndexOf(item); + BMenuItem *query = new BMenuItem("Edit Query", new BMessage(kEditQuery), 'G'); + item->Menu()->AddItem(query, itemIndex + 1); + query->SetTarget(PoseView()); + } + } +} + + +void +BContainerWindow::SetupOpenWithMenu(BMenu *parent) +{ + // start by removing nav item (and separator) from old menu + if (fOpenWithItem) { + BMenu *menu = fOpenWithItem->Menu(); + if (menu) + menu->RemoveItem(fOpenWithItem); + + delete fOpenWithItem; + fOpenWithItem = 0; + } + + if (PoseView()->SelectionList()->CountItems() == 0) + // no selection, nothing to open + return; + + if (TargetModel()->IsRoot()) + // don't add ourselves if we are root + return; + + // ToDo: + // check if only item in selection list is the root + // and do not add if true + + // add after "Open" + BMenuItem *item = parent->FindItem(kOpenSelection); + + int32 count = PoseView()->SelectionList()->CountItems(); + if (!count) + return; + + // build a list of all refs to open + BMessage message(B_REFS_RECEIVED); + for (int32 index = 0; index < count; index++) { + BPose *pose = PoseView()->SelectionList()->ItemAt(index); + message.AddRef("refs", pose->TargetModel()->EntryRef()); + } + + // add Tracker token so that refs received recipients can script us + message.AddMessenger("TrackerViewToken", BMessenger(PoseView())); + + int32 index = item->Menu()->IndexOf(item); + fOpenWithItem = new BMenuItem( + new OpenWithMenu("Open With"B_UTF8_ELLIPSIS, &message, this, be_app), + new BMessage(kOpenSelectionWith)); + fOpenWithItem->SetTarget(PoseView()); + fOpenWithItem->SetShortcut('O', B_COMMAND_KEY | B_CONTROL_KEY); + + item->Menu()->AddItem(fOpenWithItem, index + 1); +} + + +void +BContainerWindow::PopulateMoveCopyNavMenu(BNavMenu *navMenu, uint32 what, + const entry_ref *ref, bool addLocalOnly) +{ + BVolume volume; + BVolumeRoster volumeRoster; + BDirectory directory; + BEntry entry; + BPath path; + Model model; + dev_t device = ref->device; + + int32 volumeCount = 0; + + // count persistent writable volumes + volumeRoster.Rewind(); + while (volumeRoster.GetNextVolume(&volume) == B_OK) + if (!volume.IsReadOnly() && volume.IsPersistent()) + volumeCount++; + + // add the current folder + if (entry.SetTo(ref) == B_OK + && entry.GetParent(&entry) == B_OK + && model.SetTo(&entry) == B_OK) { + BNavMenu *menu = new BNavMenu("Current Folder",what,this); + menu->SetNavDir(model.EntryRef()); + menu->SetShowParent(true); + + BMenuItem *item = new SpecialModelMenuItem(&model,menu); + item->SetMessage(new BMessage((uint32)what)); + + navMenu->AddItem(item); + } + + // add the recent folder menu + // the "Tracker" settings directory is only used to get its icon + if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK) { + path.Append("Tracker"); + if (entry.SetTo(path.Path()) == B_OK + && model.SetTo(&entry) == B_OK) { + BMenu *menu = new RecentsMenu("Recent Folders",kRecentFolders,what,this); + + BMenuItem *item = new SpecialModelMenuItem(&model,menu); + item->SetMessage(new BMessage((uint32)what)); + + navMenu->AddItem(item); + } + } + + // add Desktop + FSGetBootDeskDir(&directory); + if (directory.InitCheck() == B_OK + && directory.GetEntry(&entry) == B_OK + && model.SetTo(&entry) == B_OK) + navMenu->AddNavDir(&model, what, this, true); + // ask NavMenu to populate submenu for us + + // add the home dir + if (find_directory(B_USER_DIRECTORY, &path) == B_OK + && entry.SetTo(path.Path()) == B_OK + && model.SetTo(&entry) == B_OK) + navMenu->AddNavDir(&model, what, this, true); + + navMenu->AddSeparatorItem(); + + // either add all mounted volumes (for copy), or all the top-level + // directories from the same device (for move) + // ToDo: can be changed if cross-device moves are implemented + + if (addLocalOnly || volumeCount < 2) { + // add volume this item lives on + if (volume.SetTo(device) == B_OK + && volume.GetRootDirectory(&directory) == B_OK + && directory.GetEntry(&entry) == B_OK + && model.SetTo(&entry) == B_OK) { + navMenu->AddNavDir(&model, what, this, false); + // do not have submenu populated + + navMenu->SetNavDir(model.EntryRef()); + } + } else { + // add all persistent writable volumes + volumeRoster.Rewind(); + while (volumeRoster.GetNextVolume(&volume) == B_OK) { + if (volume.IsReadOnly() || !volume.IsPersistent()) + continue; + + // add root dir + if (volume.GetRootDirectory(&directory) == B_OK + && directory.GetEntry(&entry) == B_OK + && model.SetTo(&entry) == B_OK) + navMenu->AddNavDir(&model, what, this, true); + // ask NavMenu to populate submenu for us + } + } +} + + +void +BContainerWindow::SetupMoveCopyMenus(const entry_ref *item_ref, BMenu *parent) +{ + if (IsTrash() || InTrash() || IsPrintersDir() || !fMoveToItem || !fCopyToItem || !fCreateLinkItem) + return; + + // Grab the modifiers state since we use it twice + uint32 modifierKeys = modifiers(); + + // re-parent items to this menu since they're shared + int32 index = parent->CountItems() - 7; + if (index > 0 && dynamic_cast(parent->ItemAt(index - 1)) == NULL) { + // The items below the items to be added vary in number, so + // this little "hack" makes sure they are always in place + index++; + } else + index = 0; + + if (fMoveToItem->Menu() != parent) { + if (fMoveToItem->Menu()) + fMoveToItem->Menu()->RemoveItem(fMoveToItem); + + parent->AddItem(fMoveToItem, index++); + } + + if (fCopyToItem->Menu() != parent) { + if (fCopyToItem->Menu()) + fCopyToItem->Menu()->RemoveItem(fCopyToItem); + + parent->AddItem(fCopyToItem, index++); + } + + if (fCreateLinkItem->Menu() != parent) { + if (fCreateLinkItem->Menu()) + fCreateLinkItem->Menu()->RemoveItem(fCreateLinkItem); + + parent->AddItem(fCreateLinkItem, index); + } + + // Set the "Create Link" item label here so it + // appears correctly when menus are disabled, too. + if (modifierKeys & B_SHIFT_KEY) + fCreateLinkItem->SetLabel("Create Relative Link"); + else + fCreateLinkItem->SetLabel("Create Link"); + + // only enable once the menus are built + fMoveToItem->SetEnabled(false); + fCopyToItem->SetEnabled(false); + fCreateLinkItem->SetEnabled(false); + + // get ref for item which is selected + BEntry entry; + if (entry.SetTo(item_ref) != B_OK) + return; + + Model tempModel(&entry); + if (tempModel.InitCheck() != B_OK) + return; + + if (tempModel.IsRoot() || tempModel.IsVolume()) + return; + + // configure "Move to" menu item + PopulateMoveCopyNavMenu(dynamic_cast(fMoveToItem->Submenu()), + kMoveSelectionTo, item_ref, true); + + // configure "Copy to" menu item + // add all mounted volumes (except the one this item lives on) + PopulateMoveCopyNavMenu(dynamic_cast(fCopyToItem->Submenu()), + kCopySelectionTo, item_ref, false); + + // Set "Create Link" menu item message and + // add all mounted volumes (except the one this item lives on) + if (modifierKeys & B_SHIFT_KEY) { + fCreateLinkItem->SetMessage(new BMessage(kCreateRelativeLink)); + PopulateMoveCopyNavMenu(dynamic_cast(fCreateLinkItem->Submenu()), + kCreateRelativeLink, item_ref, false); + } else { + fCreateLinkItem->SetMessage(new BMessage(kCreateLink)); + PopulateMoveCopyNavMenu(dynamic_cast(fCreateLinkItem->Submenu()), + kCreateLink, item_ref, false); + } + + fMoveToItem->SetEnabled(true); + fCopyToItem->SetEnabled(true); + fCreateLinkItem->SetEnabled(true); +} + + +uint32 +BContainerWindow::ShowDropContextMenu(BPoint loc) +{ + BPoint global(loc); + + PoseView()->ConvertToScreen(&global); + PoseView()->CommitActivePose(); + BRect mouseRect(global.x, global.y, global.x, global.y); + mouseRect.InsetBy(-5, -5); + + // Change the "Create Link" item - allow user to + // create relative links with the Shift key down. + BMenuItem *item = fDropContextMenu->FindItem(kCreateLink); + if (item == NULL) + item = fDropContextMenu->FindItem(kCreateRelativeLink); + if (item && (modifiers() & B_SHIFT_KEY)) { + item->SetLabel("Create Relative Link Here"); + item->SetMessage(new BMessage(kCreateRelativeLink)); + } else if (item) { + item->SetLabel("Create Link Here"); + item->SetMessage(new BMessage(kCreateLink)); + } + + item = fDropContextMenu->Go(global, true, true, mouseRect); + if (item) + return item->Command(); + + return 0; +} + + +void +BContainerWindow::ShowContextMenu(BPoint loc, const entry_ref *ref, BView *) +{ + ASSERT(IsLocked()); + BPoint global(loc); + PoseView()->ConvertToScreen(&global); + PoseView()->CommitActivePose(); + BRect mouseRect(global.x, global.y, global.x, global.y); + mouseRect.InsetBy(-5, -5); + + if (ref) { + // clicked on a pose, show file or volume context menu + Model model(ref); + + bool showAsVolume = false; + bool filePanel = PoseView()->IsFilePanel(); + + if (Dragging()) { + fContextMenu = NULL; + + BEntry entry; + model.GetEntry(&entry); + // + // only show for directories (directory, volume, root) + // + // don't show a popup for the trash or printers + // trash is handled in DeskWindow + // + // since this menu is opened asynchronously + // we need to make sure we don't open it more + // than once, the IsShowing flag is set in + // SlowContextPopup::AttachedToWindow and + // reset in DetachedFromWindow + // see the notes in SlowContextPopup::AttachedToWindow + // + if (!FSIsPrintersDir(&entry) && !fDragContextMenu->IsShowing()) { + // printf("ShowContextMenu - target is %s %i\n", ref->name, IsShowing(ref)); + fDragContextMenu->ClearMenu(); + // + // in case the ref is a symlink, resolve it + // only pop open for directories + BEntry resolvedEntry(ref, true); + if (!resolvedEntry.IsDirectory()) + return; + + entry_ref resolvedRef; + resolvedEntry.GetRef(&resolvedRef); + + // use the resolved ref for the menu + fDragContextMenu->SetNavDir(&resolvedRef); + fDragContextMenu->SetTypesList(fCachedTypesList); + fDragContextMenu->SetTarget(BMessenger(this)); + BPoseView *poseView = PoseView(); + if (poseView) { + BMessenger tmpTarget(poseView); + fDragContextMenu->InitTrackingHook( + &BPoseView::MenuTrackingHook, &tmpTarget, fDragMessage); + } + + // this is now asynchronous so that we don't + // deadlock in Window::Quit, + fDragContextMenu->Go(global, true, false, true); + } + return; + } else if (TargetModel()->IsRoot() || model.IsVolume()) { + fContextMenu = fVolumeContextMenu; + showAsVolume = true; + } else + fContextMenu = fFileContextMenu; + + // clean up items from last context menu + + if (fContextMenu) { + if (fContextMenu->Window()) + return; + else + MenusEnded(); + + if (model.InitCheck() == B_OK) { // ??? Do I need this ??? + if (showAsVolume) { + // non-volume enable/disable copy, move, identify + EnableNamedMenuItem(fContextMenu, kDuplicateSelection, false); + EnableNamedMenuItem(fContextMenu, kMoveToTrash, false); + EnableNamedMenuItem(fContextMenu, kIdentifyEntry, false); + + // volume model, enable/disable the Unmount item + bool ejectableVolumeSelected = false; + + BVolume boot; + BVolumeRoster().GetBootVolume(&boot); + BVolume volume; + volume.SetTo(model.NodeRef()->device); + if (volume != boot) + ejectableVolumeSelected = true; + + EnableNamedMenuItem(fContextMenu, "Unmount", ejectableVolumeSelected); + } + } + + SetupNavigationMenu(ref, fContextMenu); + if (!showAsVolume && !filePanel) { + SetupMoveCopyMenus(ref, fContextMenu); + SetupOpenWithMenu(fContextMenu); + } + + UpdateMenu(fContextMenu, kPosePopUpContext); + + fContextMenu->Go(global, true, false, mouseRect, true); + } + } else if (fWindowContextMenu) { + if (fWindowContextMenu->Window()) + return; + + MenusEnded(); + + // clicked on a window, show window context menu + + SetupNavigationMenu(ref, fWindowContextMenu); + UpdateMenu(fWindowContextMenu, kWindowPopUpContext); + + fWindowContextMenu->Go(global, true, false, mouseRect, true); + } + fContextMenu = NULL; +} + + +void +BContainerWindow::AddFileContextMenus(BMenu *menu) +{ + menu->AddItem(new BMenuItem("Open", new BMessage(kOpenSelection), 'O')); + menu->AddItem(new BMenuItem("Get Info", new BMessage(kGetInfo), 'I')); + menu->AddItem(new BMenuItem("Edit Name", new BMessage(kEditItem), 'E')); + + if (!IsTrash() && !InTrash() && !IsPrintersDir()) + menu->AddItem(new BMenuItem("Duplicate", + new BMessage(kDuplicateSelection), 'D')); + + if (!IsTrash() && !InTrash()) { + menu->AddItem(new BMenuItem(TrackerSettings().DontMoveFilesToTrash() ? + "Delete" : "Move to Trash", + new BMessage(kMoveToTrash), 'T')); + + // add separator for copy to/move to items (navigation items) + menu->AddSeparatorItem(); + } else { + menu->AddItem(new BMenuItem("Delete", new BMessage(kDelete), 0)); + menu->AddItem(new BMenuItem("Restore", new BMessage(kRestoreFromTrash), 0)); + } + + menu->AddSeparatorItem(); + BMenuItem *cutItem, *copyItem; + menu->AddItem(cutItem = new BMenuItem("Cut", new BMessage(B_CUT), 'X')); + menu->AddItem(copyItem = new BMenuItem("Copy", new BMessage(B_COPY), 'C')); + + menu->AddSeparatorItem(); + menu->AddItem(new BMenuItem("Identify", new BMessage(kIdentifyEntry))); + BMenu *addOnMenuItem = new BMenu(kAddOnsMenuName); + addOnMenuItem->SetFont(be_plain_font); + menu->AddItem(addOnMenuItem); + + // set targets as needed + menu->SetTargetForItems(PoseView()); + cutItem->SetTarget(this); + copyItem->SetTarget(this); +} + + +void +BContainerWindow::AddVolumeContextMenus(BMenu *menu) +{ + menu->AddItem(new BMenuItem("Open", new BMessage(kOpenSelection), 'O')); + menu->AddItem(new BMenuItem("Get Info", new BMessage(kGetInfo), 'I')); + menu->AddItem(new BMenuItem("Edit Name", new BMessage(kEditItem), 'E')); + + menu->AddSeparatorItem(); + menu->AddItem(new MountMenu("Mount")); + + BMenuItem *item = new BMenuItem("Unmount", new BMessage(kUnmountVolume), 'U'); + item->SetEnabled(false); + menu->AddItem(item); + + menu->AddSeparatorItem(); + menu->AddItem(new BMenu(kAddOnsMenuName)); + + menu->SetTargetForItems(PoseView()); +} + + +void +BContainerWindow::AddWindowContextMenus(BMenu *menu) +{ + // create context sensitive menu for empty area of window + // since we check view mode before display, this should be a radio + // mode menu + + bool needSeparator = true; + if (IsTrash()) + menu->AddItem(new BMenuItem("Empty Trash", new BMessage(kEmptyTrash))); + else if (IsPrintersDir()) + menu->AddItem(new BMenuItem("Add Printer"B_UTF8_ELLIPSIS, new BMessage(kAddPrinter), 'N')); + else if (InTrash()) + needSeparator = false; + else { + TemplatesMenu *templateMenu = new TemplatesMenu(PoseView()); + menu->AddItem(templateMenu); + templateMenu->SetTargetForItems(PoseView()); + templateMenu->SetFont(be_plain_font); + } + + if (needSeparator) + menu->AddSeparatorItem(); + + menu->AddItem(new BMenuItem("Icon View", new BMessage(kIconMode), '1')); + menu->AddItem(new BMenuItem("Mini Icon View", new BMessage(kMiniIconMode), '2')); + menu->AddItem(new BMenuItem("List View", new BMessage(kListMode), '3')); + menu->AddSeparatorItem(); + BMenuItem *pasteItem = new BMenuItem("Paste", new BMessage(B_PASTE), 'V'); + menu->AddItem(pasteItem); + menu->AddSeparatorItem(); + BMenuItem *resizeItem = new BMenuItem("Resize to Fit", + new BMessage(kResizeToFit), 'Y'); + menu->AddItem(resizeItem); + menu->AddItem(new BMenuItem("Clean Up", new BMessage(kCleanup), 'K')); + menu->AddItem(new BMenuItem("Select"B_UTF8_ELLIPSIS, + new BMessage(kShowSelectionWindow), 'A', B_SHIFT_KEY)); + menu->AddItem(new BMenuItem("Select All", new BMessage(B_SELECT_ALL), 'A')); + if (!IsTrash()) + menu->AddItem(new BMenuItem("Open Parent", new BMessage(kOpenParentDir), + B_UP_ARROW)); + + BMenuItem *closeItem = new BMenuItem("Close", new BMessage(B_QUIT_REQUESTED), + 'W'); + menu->AddItem(closeItem); + menu->AddSeparatorItem(); + BMenu *addOnMenuItem = new BMenu(kAddOnsMenuName); + addOnMenuItem->SetFont(be_plain_font); + menu->AddItem(addOnMenuItem); + +#if DEBUG + menu->AddSeparatorItem(); + BMenuItem *testing = new BMenuItem("Test Icon Cache", new BMessage(kTestIconCache)); + menu->AddItem(testing); +#endif + + // target items as needed + menu->SetTargetForItems(PoseView()); + closeItem->SetTarget(this); + resizeItem->SetTarget(this); + pasteItem->SetTarget(this); +} + + +void +BContainerWindow::AddDropContextMenus(BMenu *menu) +{ + menu->AddItem(new BMenuItem("Create Link Here", new BMessage(kCreateLink))); + menu->AddItem(new BMenuItem("Move Here", new BMessage(kMoveSelectionTo))); + menu->AddItem(new BMenuItem("Copy Here", new BMessage(kCopySelectionTo))); + menu->AddSeparatorItem(); + menu->AddItem(new BMenuItem("Cancel", new BMessage(kCancelButton))); +} + + +void +BContainerWindow::EachAddon(bool (*eachAddon)(const Model *, const char *, + uint32 shortcut, bool primary, void *context), void *passThru) +{ + BObjectList uniqueList(10, true); + BPath path; + bool bail = false; + if (find_directory(B_BEOS_ADDONS_DIRECTORY, &path) == B_OK) + bail = EachAddon(path, eachAddon, &uniqueList, passThru); + + if (!bail && find_directory(B_USER_ADDONS_DIRECTORY, &path) == B_OK) + bail = EachAddon(path, eachAddon, &uniqueList, passThru); + + if (!bail && find_directory(B_COMMON_ADDONS_DIRECTORY, &path) == B_OK) + EachAddon(path, eachAddon, &uniqueList, passThru); +} + + +bool +BContainerWindow::EachAddon(BPath &path, bool (*eachAddon)(const Model *, + const char *, uint32 shortcut, bool primary, void *), + BObjectList *uniqueList, void *params) +{ + path.Append("Tracker"); + + BDirectory dir; + BEntry entry; + + if (dir.SetTo(path.Path()) != B_OK) + return false; + + // build a list of the MIME types of the selected items + + BObjectList mimeTypes(10, true); + + int32 count = PoseView()->SelectionList()->CountItems(); + if (!count) { + // just add the type of the current directory + AddMimeTypeString(mimeTypes, TargetModel()); + } else { + for (int32 index = 0; index < count; index++) { + BPose *pose = PoseView()->SelectionList()->ItemAt(index); + AddMimeTypeString(mimeTypes, pose->TargetModel()); + } + } + + dir.Rewind(); + while (dir.GetNextEntry(&entry) == B_OK) { + bool primary = false; + + if (entry.IsSymLink()) { + // resolve symlinks if needed + entry_ref ref; + entry.GetRef(&ref); + entry.SetTo(&ref, true); + } + + Model *model = new Model(&entry); + if (model->InitCheck() != B_OK || !model->IsExecutable()) { + delete model; + continue; + } + + // check if it supports at least one of the selected entries + + if (mimeTypes.CountItems()) { + BFile file(&entry, B_READ_ONLY); + if (file.InitCheck() == B_OK) { + BAppFileInfo info(&file); + if (info.InitCheck() == B_OK) { + bool secondary = true; + + // does this add-on has types set at all? + BMessage message; + if (info.GetSupportedTypes(&message) == B_OK) { + type_code type; + int32 count; + if (message.GetInfo("types", &type, &count) == B_OK) + secondary = false; + } + + // check all supported types if it has some set + if (!secondary) { + for (int32 i = mimeTypes.CountItems(); !primary && i-- > 0;) { + BString *type = mimeTypes.ItemAt(i); + if (info.IsSupportedType(type->String())) { + BMimeType mimeType(type->String()); + if (info.Supports(&mimeType)) + primary = true; + else + secondary = true; + } + } + } + + if (!secondary && !primary) { + delete model; + continue; + } + } + } + } + + char name[B_FILE_NAME_LENGTH]; + uint32 key; + StripShortcut(model, name, key); + + // do a uniqueness check + if (uniqueList->EachElement(MatchOne, name)) { + // found one already in the list + delete model; + continue; + } + uniqueList->AddItem(model); + + if ((eachAddon)(model, name, key, primary, params)) + return true; + } + return false; +} + + +void +BContainerWindow::BuildAddOnMenu(BMenu *menu) +{ + BMenuItem *item = menu->FindItem(kAddOnsMenuName); + if (menu->IndexOf(item) == 0) { + // the folder of the context menu seems to be named "Add-Ons" + // so we just take the last menu item, which is correct if not + // build with debug option + item = menu->ItemAt(menu->CountItems() - 1); + } + if (item == NULL) + return; + + menu = item->Submenu(); + if (!menu) + return; + + menu->SetFont(be_plain_font); + + // found the addons menu, empty it first + for (;;) { + item = menu->RemoveItem(0L); + if (!item) + break; + delete item; + } + + BObjectList primaryList; + BObjectList secondaryList; + + AddOneAddonParams params; + params.primaryList = &primaryList; + params.secondaryList = &secondaryList; + + EachAddon(AddOneAddon, ¶ms); + + primaryList.SortItems(CompareLabels); + secondaryList.SortItems(CompareLabels); + + int32 count = primaryList.CountItems(); + for (int32 index = 0; index < count; index++) + menu->AddItem(primaryList.ItemAt(index)); + + if (count != 0) + menu->AddSeparatorItem(); + + count = secondaryList.CountItems(); + for (int32 index = 0; index < count; index++) + menu->AddItem(secondaryList.ItemAt(index)); + + menu->SetTargetForItems(this); +} + + +void +BContainerWindow::UpdateMenu(BMenu *menu, UpdateMenuContext context) +{ + const int32 selectCount = PoseView()->SelectionList()->CountItems(); + const int32 count = PoseView()->CountItems(); + + if (context == kMenuBarContext) { + EnableNamedMenuItem(menu, kOpenSelection, selectCount > 0); + EnableNamedMenuItem(menu, kGetInfo, selectCount > 0); + EnableNamedMenuItem(menu, kIdentifyEntry, selectCount > 0); + EnableNamedMenuItem(menu, kMoveToTrash, selectCount > 0); + EnableNamedMenuItem(menu, kRestoreFromTrash, selectCount > 0); + EnableNamedMenuItem(menu, kDelete, selectCount > 0); + EnableNamedMenuItem(menu, kDuplicateSelection, selectCount > 0); + } + + if (context == kMenuBarContext || context == kPosePopUpContext) { + SetUpEditQueryItem(menu); + EnableNamedMenuItem(menu, kEditItem, selectCount == 1 + && (context == kPosePopUpContext || !PoseView()->ActivePose())); + SetCutItem(menu); + SetCopyItem(menu); + SetPasteItem(menu); + } + + if (context == kMenuBarContext || context == kWindowPopUpContext) { + MarkNamedMenuItem(menu, kIconMode, PoseView()->ViewMode() == kIconMode); + MarkNamedMenuItem(menu, kListMode, PoseView()->ViewMode() == kListMode); + MarkNamedMenuItem(menu, kMiniIconMode, + PoseView()->ViewMode() == kMiniIconMode); + + SetCloseItem(menu); + SetCleanUpItem(menu); + SetPasteItem(menu); + + EnableNamedMenuItem(menu, kOpenParentDir, !TargetModel()->IsRoot()); + EnableNamedMenuItem(menu, kEmptyTrash, count > 0); + EnableNamedMenuItem(menu, B_SELECT_ALL, count > 0); + + BMenuItem *item = menu->FindItem(kTemplatesMenuName); + if (item) { + TemplatesMenu *templateMenu = dynamic_cast( + item->Submenu()); + if (templateMenu) + templateMenu->UpdateMenuState(); + } + } + + BuildAddOnMenu(menu); +} + + +void +BContainerWindow::LoadAddOn(BMessage *message) +{ + UpdateIfNeeded(); + + entry_ref addonRef; + status_t result = message->FindRef("refs", &addonRef); + if (result != B_OK) { + char buffer[1024]; + sprintf(buffer, "Error %s loading Add-On %s.", strerror(result), addonRef.name); + (new BAlert("", buffer, "Cancel", 0, 0, + B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); + return; + } + + // add selected refs to message + BMessage *refs = new BMessage(B_REFS_RECEIVED); + + BObjectList *list = PoseView()->SelectionList(); + + int32 index = 0; + BPose *pose; + while ((pose = list->ItemAt(index++)) != NULL) + refs->AddRef("refs", pose->TargetModel()->EntryRef()); + + refs->AddMessenger("TrackerViewToken", BMessenger(PoseView())); + + LaunchInNewThread("Add-on", B_NORMAL_PRIORITY, &AddOnThread, refs, addonRef, + *TargetModel()->EntryRef()); +} + + +BMenuItem * +BContainerWindow::NewAttributeMenuItem(const char *label, const char *attrName, + int32 attrType, float attrWidth, int32 attrAlign, bool attrEditable, bool attrStatField) +{ + BMessage *message = new BMessage(kAttributeItem); + message->AddString("attr_name", attrName); + message->AddInt32("attr_type", attrType); + message->AddInt32("attr_hash", (int32)AttrHashString(attrName, (uint32)attrType)); + message->AddFloat("attr_width", attrWidth); + message->AddInt32("attr_align", attrAlign); + message->AddBool("attr_editable", attrEditable); + message->AddBool("attr_statfield", attrStatField); + + BMenuItem *menuItem = new BMenuItem(label, message); + menuItem->SetTarget(PoseView()); + + return menuItem; +} + + +void +BContainerWindow::NewAttributeMenu(BMenu *menu) +{ + ASSERT(PoseView()); + + BMenuItem *item; + menu->AddItem(item = new BMenuItem("Copy Attributes", new BMessage(kCopyAttributes))); + item->SetTarget(PoseView()); + menu->AddItem(item = new BMenuItem("Paste Attributes", new BMessage(kPasteAttributes))); + item->SetTarget(PoseView()); + menu->AddSeparatorItem(); + + menu->AddItem(NewAttributeMenuItem ("Name", kAttrStatName, B_STRING_TYPE, + 145, B_ALIGN_LEFT, true, true)); + + menu->AddItem(NewAttributeMenuItem ("Size", kAttrStatSize, B_OFF_T_TYPE, + 80, B_ALIGN_RIGHT, false, true)); + + menu->AddItem(NewAttributeMenuItem ("Modified", kAttrStatModified, B_TIME_TYPE, + 150, B_ALIGN_LEFT, false, true)); + + menu->AddItem(NewAttributeMenuItem ("Created", kAttrStatCreated, B_TIME_TYPE, + 150, B_ALIGN_LEFT, false, true)); + + menu->AddItem(NewAttributeMenuItem ("Kind", kAttrMIMEType, B_MIME_STRING_TYPE, + 145, B_ALIGN_LEFT, false, false)); + + if (IsTrash() || InTrash()) + menu->AddItem(NewAttributeMenuItem ("Original name", kAttrOriginalPath, B_STRING_TYPE, + 225, B_ALIGN_LEFT, false, false)); + else + menu->AddItem(NewAttributeMenuItem ("Path", kAttrPath, B_STRING_TYPE, + 225, B_ALIGN_LEFT, false, false)); + +#ifdef OWNER_GROUP_ATTRIBUTES + menu->AddItem(NewAttributeMenuItem ("Owner", kAttrStatOwner, B_STRING_TYPE, + 60, B_ALIGN_LEFT, false, true)); + + menu->AddItem(NewAttributeMenuItem ("Group", kAttrStatGroup, B_STRING_TYPE, + 60, B_ALIGN_LEFT, false, true)); +#endif + + menu->AddItem(NewAttributeMenuItem ("Permissions", kAttrStatMode, B_STRING_TYPE, + 80, B_ALIGN_LEFT, false, true)); +} + + +void +BContainerWindow::ShowAttributeMenu() +{ + ASSERT(fAttrMenu); + fMenuBar->AddItem(fAttrMenu); +} + + +void +BContainerWindow::HideAttributeMenu() +{ + ASSERT(fAttrMenu); + fMenuBar->RemoveItem(fAttrMenu); +} + + +void +BContainerWindow::MarkAttributeMenu() +{ + MarkAttributeMenu(fAttrMenu); +} + + +void +BContainerWindow::MarkAttributeMenu(BMenu *menu) +{ + if (!menu) + return; + + int32 count = menu->CountItems(); + for (int32 index = 0; index < count; index++) { + BMenuItem *item = menu->ItemAt(index); + int32 attrHash; + if (item->Message()) + if (item->Message()->FindInt32("attr_hash", &attrHash) == B_OK) + item->SetMarked(PoseView()->ColumnFor((uint32)attrHash) != 0); + else + item->SetMarked(false); + + BMenu *submenu = item->Submenu(); + if (submenu) { + int32 count2 = submenu->CountItems(); + for (int32 subindex = 0; subindex < count2; subindex++) { + item = submenu->ItemAt(subindex); + if (item->Message()) + if (item->Message()->FindInt32("attr_hash", &attrHash) == B_OK) + item->SetMarked(PoseView()->ColumnFor((uint32)attrHash) != 0); + else + item->SetMarked(false); + } + } + } +} + + +void +BContainerWindow::AddMimeTypesToMenu() +{ + AddMimeTypesToMenu(fAttrMenu); +} + + +void +BContainerWindow::AddMimeTypesToMenu(BMenu *menu) +{ + if (!menu) + return; + + // find start of mime types in menu + int32 count = menu->CountItems(); + int32 start; + + for (start = 0; start < count; start++) { + if (menu->ItemAt(start)->Submenu()) + break; + } + + // Remove old mime menu: + int32 removeIndex = count - 1; + while (menu->ItemAt(removeIndex)->Submenu() != NULL) { + delete menu->RemoveItem(removeIndex); + removeIndex--; + } + + // Add a separator item if there is none yet + if (dynamic_cast(menu->ItemAt(removeIndex)) == NULL) + menu->AddSeparatorItem(); + + int32 typeCount = PoseView()->CountMimeTypes(); + + for (int32 index = 0; index < typeCount; index++) { + + bool shouldAdd = true; + const char *signature = PoseView()->MimeTypeAt(index); + + for (int32 subindex = start; subindex < count; subindex++) { + BMenuItem *item = menu->ItemAt(subindex); + if (!item) + continue; + BMessage *message = item->Message(); + if (!message) + continue; + const char *str; + if (message->FindString("mimetype", &str) == B_OK + && strcmp(signature, str) == 0) { + shouldAdd = false; + break; + } + } + + if (shouldAdd) { + BMessage attr_msg; + char desc[B_MIME_TYPE_LENGTH]; + const char *nameToAdd = signature; + + BMimeType mimetype(signature); + + if (!mimetype.IsInstalled()) + continue; + + // only add things to menu which have "user-visible" data + if (mimetype.GetAttrInfo(&attr_msg) != B_OK) + continue; + + if (mimetype.GetShortDescription(desc) == B_OK && desc[0]) + nameToAdd = desc; + + // go through each field in meta mime and add it to a menu + BMenu *localMenu = 0; + int32 index = -1; + const char *str; + + while (attr_msg.FindString("attr:public_name", ++index, &str) == B_OK) { + if (!attr_msg.FindBool("attr:viewable", index)) + // don't add if attribute not viewable + continue; + + int32 type; + int32 align; + int32 width; + bool editable; + + const char *attrName; + + if (attr_msg.FindString("attr:name", index, &attrName) != B_OK) + continue; + + if (attr_msg.FindInt32("attr:type", index, &type) != B_OK) + continue; + + if (attr_msg.FindBool("attr:editable", index, &editable) != B_OK) + continue; + + if (attr_msg.FindInt32("attr:width", index, &width) != B_OK) + continue; + + if (attr_msg.FindInt32("attr:alignment", index, &align) != B_OK) + continue; + + if (!localMenu) { + // do a lazy allocation of the menu + localMenu = new BMenu(nameToAdd); + BFont font; + menu->GetFont(&font); + localMenu->SetFont(&font); + } + localMenu->AddItem(NewAttributeMenuItem (str, attrName, type, + width, align, editable, false)); + } + if (localMenu) { + BMessage *message = new BMessage(kMIMETypeItem); + message->AddString("mimetype", signature); + menu->AddItem(new IconMenuItem(localMenu, message, signature, B_MINI_ICON)); + } + } + } + + // remove separator if it's the only item in menu + BMenuItem *item = menu->ItemAt(menu->CountItems() - 1); + if (dynamic_cast(item) != NULL) { + menu->RemoveItem(item); + delete item; + } + + MarkAttributeMenu(menu); +} + + +BHandler * +BContainerWindow::ResolveSpecifier(BMessage *message, int32 index, + BMessage *specifier, int32 form, const char *property) +{ + if (strcmp(property, "Poses") == 0) { +// PRINT(("BContainerWindow::ResolveSpecifier %s\n", property)); + message->PopSpecifier(); + return PoseView(); + } + + return _inherited::ResolveSpecifier(message, index, specifier, + form, property); +} + + +PiggybackTaskLoop * +BContainerWindow::DelayedTaskLoop() +{ + if (!fTaskLoop) + fTaskLoop = new PiggybackTaskLoop; + + return fTaskLoop; +} + + +bool +BContainerWindow::NeedsDefaultStateSetup() +{ + if (!TargetModel()) + return false; + + if (TargetModel()->IsRoot()) + // don't try to set up anything if we are root + return false; + + WindowStateNodeOpener opener(this, false); + if (!opener.StreamNode()) + // can't read state, give up + return false; + + return !NodeHasSavedState(opener.Node()); +} + + +bool +BContainerWindow::DefaultStateSourceNode(const char *name, BNode *result, + bool createNew, bool createFolder) +{ +// PRINT(("looking for default state in tracker settings dir\n")); + BPath settingsPath; + if (FSFindTrackerSettingsDir(&settingsPath) != B_OK) + return false; + + BDirectory dir(settingsPath.Path()); + + BPath path(settingsPath); + path.Append(name); + if (!BEntry(path.Path()).Exists()) { + + if (!createNew) + return false; + + BPath tmpPath(settingsPath); + for (;;) { + // deal with several levels of folders + const char *nextSlash = strchr(name, '/'); + if (!nextSlash) + break; + + BString tmp; + tmp.SetTo(name, nextSlash - name); + tmpPath.Append(tmp.String()); + + + mkdir(tmpPath.Path(), 0777); + + name = nextSlash + 1; + if (!name[0]) { + // can't deal with a slash at end + + return false; + } + } + + if (createFolder) { + if (mkdir(path.Path(), 0777) < 0) + return false; + } else { + BFile file; + if (dir.CreateFile(name, &file) != B_OK) + return false; + } + } + +// PRINT(("using default state from %s\n", path.Path())); + result->SetTo(path.Path()); + return result->InitCheck() == B_OK; +} + + +void +BContainerWindow::SetUpDefaultState() +{ + BNode defaultingNode; + // this is where we'll ulitimately get the state from + bool gotDefaultingNode = 0; + bool shouldStagger = false; + + ASSERT(TargetModel()); + + PRINT(("folder %s does not have any saved state\n", TargetModel()->Name())); + + WindowStateNodeOpener opener(this, true); + // this is our destination node, whatever it is for this window + if (!opener.StreamNode()) + return; + + if (!TargetModel()->IsRoot()) { + BDirectory desktop; + FSGetDeskDir(&desktop, TargetModel()->EntryRef()->device); + + // try copying state from our parent directory, unless it is the desktop folder + BEntry entry(TargetModel()->EntryRef()); + BDirectory parent; + if (entry.GetParent(&parent) == B_OK && parent != desktop) { + PRINT(("looking at parent for state\n")); + if (NodeHasSavedState(&parent)) { + PRINT(("got state from parent\n")); + defaultingNode = parent; + gotDefaultingNode = true; + // when getting state from parent, stagger the window + shouldStagger = true; + } + } + } + + if (!gotDefaultingNode + // parent didn't have any state, use the template directory from + // tracker settings folder for what our state should be + // For simplicity we are not picking up the most recent + // changes that didn't get committed if home is still open in + // a window, that's probably not a problem; would be OK if state got committed + // after every change + && !DefaultStateSourceNode(kDefaultFolderTemplate, &defaultingNode, true)) + return; + + // copy over the attributes + + // set up a filter of the attributes we want copied + const char *allowAttrs[] = { + kAttrWindowFrame, + kAttrWindowWorkspace, + kAttrViewState, + kAttrViewStateForeign, + kAttrColumns, + kAttrColumnsForeign, + 0 + }; + + // copy over attributes that apply; transform them properly, stripping + // parts that do not apply, adding a window stagger, etc. + + StaggerOneParams params; + params.rectFromParent = shouldStagger; + SelectiveAttributeTransformer frameOffsetter(kAttrWindowFrame, OffsetFrameOne, ¶ms); + SelectiveAttributeTransformer scrollOriginCleaner(kAttrViewState, + ClearViewOriginOne, ¶ms); + + // do it + AttributeStreamMemoryNode memoryNode; + NamesToAcceptAttrFilter filter(allowAttrs); + AttributeStreamFileNode fileNode(&defaultingNode); + + *opener.StreamNode() << scrollOriginCleaner << frameOffsetter + << memoryNode << filter << fileNode; +} + + +void +BContainerWindow::RestoreWindowState(AttributeStreamNode *node) +{ + if (!node || dynamic_cast(this)) + // don't restore any window state if we are a desktop window + return; + + const char *rectAttributeName; + const char *workspaceAttributeName; + if (TargetModel()->IsRoot()) { + rectAttributeName = kAttrDisksFrame; + workspaceAttributeName = kAttrDisksWorkspace; + } else { + rectAttributeName = kAttrWindowFrame; + workspaceAttributeName = kAttrWindowWorkspace; + } + + BRect frame(Frame()); + if (node->Read(rectAttributeName, 0, B_RECT_TYPE, sizeof(BRect), &frame) == sizeof(BRect)) { + MoveTo(frame.LeftTop()); + ResizeTo(frame.Width(), frame.Height()); + } else + sNewWindRect.OffsetBy(kWindowStaggerBy, kWindowStaggerBy); + + uint32 workspace; + if (node->Read(workspaceAttributeName, 0, B_INT32_TYPE, sizeof(uint32), &workspace) == sizeof(uint32) + && (fContainerWindowFlags & kRestoreWorkspace)) + SetWorkspaces(workspace); + + if (fContainerWindowFlags & kIsHidden) + Minimize(true); +} + + +void +BContainerWindow::RestoreWindowState(const BMessage &message) +{ + if (dynamic_cast(this)) + // don't restore any window state if we are a desktop window + return; + + const char *rectAttributeName; + const char *workspaceAttributeName; + if (TargetModel()->IsRoot()) { + rectAttributeName = kAttrDisksFrame; + workspaceAttributeName = kAttrDisksWorkspace; + } else { + rectAttributeName = kAttrWindowFrame; + workspaceAttributeName = kAttrWindowWorkspace; + } + + BRect frame(Frame()); + if (message.FindRect(rectAttributeName, &frame) == B_OK) { + MoveTo(frame.LeftTop()); + ResizeTo(frame.Width(), frame.Height()); + } else + sNewWindRect.OffsetBy(kWindowStaggerBy, kWindowStaggerBy); + + uint32 workspace; + + if (message.FindInt32(workspaceAttributeName, (int32 *)&workspace) == B_OK + && (fContainerWindowFlags & kRestoreWorkspace)) + SetWorkspaces(workspace); + if (fContainerWindowFlags & kIsHidden) + Minimize(true); +} + + +void +BContainerWindow::SaveWindowState(AttributeStreamNode *node) +{ + ASSERT(node); + const char *rectAttributeName; + const char *workspaceAttributeName; + if (TargetModel() && TargetModel()->IsRoot()) { + rectAttributeName = kAttrDisksFrame; + workspaceAttributeName = kAttrDisksWorkspace; + } else { + rectAttributeName = kAttrWindowFrame; + workspaceAttributeName = kAttrWindowWorkspace; + } + + // node is null if it already got deleted + BRect frame(Frame()); + node->Write(rectAttributeName, 0, B_RECT_TYPE, sizeof(BRect), &frame); + uint32 workspaces = Workspaces(); + node->Write(workspaceAttributeName, 0, B_INT32_TYPE, sizeof(uint32), + &workspaces); +} + + +void +BContainerWindow::SaveWindowState(BMessage &message) const +{ + const char *rectAttributeName; + const char *workspaceAttributeName; + + if (TargetModel() && TargetModel()->IsRoot()) { + rectAttributeName = kAttrDisksFrame; + workspaceAttributeName = kAttrDisksWorkspace; + } else { + rectAttributeName = kAttrWindowFrame; + workspaceAttributeName = kAttrWindowWorkspace; + } + + // node is null if it already got deleted + BRect frame(Frame()); + message.AddRect(rectAttributeName, frame); + message.AddInt32(workspaceAttributeName, (int32)Workspaces()); +} + + +status_t +BContainerWindow::DragStart(const BMessage *incoming) +{ + if (!incoming) + return B_ERROR; + + // if already dragging, or + // if all the refs match + if (Dragging() && SpringLoadedFolderCompareMessages(incoming, fDragMessage)) + return B_OK; + + // cache the current drag message + // build a list of the mimetypes in the message + SpringLoadedFolderCacheDragData(incoming, &fDragMessage, &fCachedTypesList); + + fWaitingForRefs = true; + + return B_OK; +} + + +void +BContainerWindow::DragStop() +{ + delete fDragMessage; + fDragMessage = NULL; + + delete fCachedTypesList; + fCachedTypesList = NULL; + + fWaitingForRefs = false; +} + + +void +BContainerWindow::ShowSelectionWindow() +{ + if (fSelectionWindow == NULL) { + fSelectionWindow = new SelectionWindow(this); + fSelectionWindow->Show(); + } else if (fSelectionWindow->Lock()) { + if (fSelectionWindow->IsHidden()) { + fSelectionWindow->MoveCloseToMouse(); + fSelectionWindow->Show(); + } + fSelectionWindow->Unlock(); + } +} + + +void +BContainerWindow::ShowNavigator(bool show) +{ + if (PoseView()->IsDesktopWindow()) + return; + + if (show) { + if (Navigator() && !Navigator()->IsHidden()) + return; + + if (Navigator() == NULL) { + BRect rect(Bounds()); + rect.top = KeyMenuBar()->Bounds().Height() + 1; + rect.bottom = rect.top + BNavigator::CalcNavigatorHeight(); + fNavigator = new BNavigator(TargetModel(), rect); + AddChild(fNavigator); + } + + if (Navigator()->IsHidden()) { + if (Navigator()->Bounds().top == 0) + Navigator()->MoveTo(0, KeyMenuBar()->Bounds().Height() + 1); + // This is if the navigator was created with a .top = 0. + Navigator()->Show(); + } + + float displacement = Navigator()->Frame().Height() + 1; + + PoseView()->MoveBy(0, displacement); + PoseView()->ResizeBy(0, -displacement); + + if (PoseView()->VScrollBar()) { + PoseView()->VScrollBar()->MoveBy(0, displacement); + PoseView()->VScrollBar()->ResizeBy(0, -displacement); + PoseView()->UpdateScrollRange(); + } + } else { + if (!Navigator() || Navigator()->IsHidden()) + return; + + float displacement = Navigator()->Frame().Height() + 1; + + PoseView()->ResizeBy(0, displacement); + PoseView()->MoveBy(0, -displacement); + + if (PoseView()->VScrollBar()) { + PoseView()->VScrollBar()->ResizeBy(0, displacement); + PoseView()->VScrollBar()->MoveBy(0, -displacement); + PoseView()->UpdateScrollRange(); + } + + fNavigator->Hide(); + } +} + + +void +BContainerWindow::SetSingleWindowBrowseShortcuts(bool enabled) +{ + if (PoseView()->IsDesktopWindow()) + return; + + if (enabled) { + if (!Navigator()) + return; + + RemoveShortcut(B_DOWN_ARROW, B_OPTION_KEY | B_COMMAND_KEY); + RemoveShortcut(B_UP_ARROW, B_COMMAND_KEY | B_OPTION_KEY); + RemoveShortcut(B_UP_ARROW, B_COMMAND_KEY | B_OPTION_KEY | B_CONTROL_KEY); + RemoveShortcut(B_UP_ARROW, B_COMMAND_KEY | B_CONTROL_KEY); + + AddShortcut(B_LEFT_ARROW, B_COMMAND_KEY, + new BMessage(kNavigatorCommandBackward), Navigator()); + AddShortcut(B_RIGHT_ARROW, B_COMMAND_KEY, + new BMessage(kNavigatorCommandForward), Navigator()); + AddShortcut(B_UP_ARROW, B_COMMAND_KEY, + new BMessage(kNavigatorCommandUp), Navigator()); + + AddShortcut(B_LEFT_ARROW, B_OPTION_KEY | B_COMMAND_KEY, + new BMessage(kNavigatorCommandBackward), Navigator()); + AddShortcut(B_RIGHT_ARROW, B_OPTION_KEY | B_COMMAND_KEY, + new BMessage(kNavigatorCommandForward), Navigator()); + AddShortcut(B_UP_ARROW, B_OPTION_KEY | B_COMMAND_KEY, + new BMessage(kNavigatorCommandUp), Navigator()); + + } else { + + RemoveShortcut(B_LEFT_ARROW, B_COMMAND_KEY); + RemoveShortcut(B_RIGHT_ARROW, B_COMMAND_KEY); + RemoveShortcut(B_UP_ARROW, B_COMMAND_KEY); + // This is added again, below, with a new meaning. + + RemoveShortcut(B_LEFT_ARROW, B_OPTION_KEY | B_COMMAND_KEY); + RemoveShortcut(B_RIGHT_ARROW, B_OPTION_KEY | B_COMMAND_KEY); + RemoveShortcut(B_UP_ARROW, B_OPTION_KEY | B_COMMAND_KEY); + // This also changes meaning, added again below. + + AddShortcut(B_DOWN_ARROW, B_OPTION_KEY | B_COMMAND_KEY, + new BMessage(kOpenSelection), PoseView()); + AddShortcut(B_UP_ARROW, B_COMMAND_KEY, + new BMessage(kOpenParentDir), PoseView()); + // We change the meaning from kNavigatorCommandUp to kOpenParentDir. + AddShortcut(B_UP_ARROW, B_COMMAND_KEY | B_OPTION_KEY, + new BMessage(kOpenParentDir), PoseView()); + AddShortcut(B_UP_ARROW, B_COMMAND_KEY | B_OPTION_KEY | B_CONTROL_KEY, + new BMessage(kOpenParentDir), PoseView()); + AddShortcut(B_UP_ARROW, B_COMMAND_KEY | B_CONTROL_KEY, + new BMessage(kOpenParentDir), PoseView()); + // the command option results in closing the parent window + // the control is a secret backdoor to get at the Disks menu + } +} + + +void +BContainerWindow::SetPathWatchingEnabled(bool enable) +{ + if (IsPathWatchingEnabled()) { + stop_watching(this); + fIsWatchingPath = false; + } + + if (enable) { + if (TargetModel() != NULL) { + BEntry entry; + + TargetModel()->GetEntry(&entry); + status_t err; + do { + err = entry.GetParent(&entry); + if (err != B_OK) + break; + + char name[B_FILE_NAME_LENGTH]; + entry.GetName(name); + if (strcmp(name, "/") == 0) + break; + + node_ref ref; + entry.GetNodeRef(&ref); + watch_node(&ref, B_WATCH_NAME, this); + } while (err == B_OK); + + fIsWatchingPath = err == B_OK; + } else + fIsWatchingPath = false; + } +} + + +void +BContainerWindow::PulseTaskLoop() +{ + if (fTaskLoop) + fTaskLoop->PulseMe(); +} + + +// #pragma mark - + + +WindowStateNodeOpener::WindowStateNodeOpener(BContainerWindow *window, bool forWriting) + : fModelOpener(NULL), + fNode(NULL), + fStreamNode(NULL) +{ + if (window->TargetModel() && window->TargetModel()->IsRoot()) { + BVolume bootVol; + BVolumeRoster().GetBootVolume(&bootVol); + BDirectory dir; + if (FSGetDeskDir(&dir, bootVol.Device()) == B_OK) { + fNode = new BDirectory(dir); + fStreamNode = new AttributeStreamFileNode(fNode); + } + } else if (window->TargetModel()){ + fModelOpener = new ModelNodeLazyOpener(window->TargetModel(), forWriting, false); + if (fModelOpener->IsOpen(forWriting)) + fStreamNode = new AttributeStreamFileNode(fModelOpener->TargetModel()->Node()); + } +} + +WindowStateNodeOpener::~WindowStateNodeOpener() +{ + delete fModelOpener; + delete fNode; + delete fStreamNode; +} + + +void +WindowStateNodeOpener::SetTo(const BDirectory *node) +{ + delete fModelOpener; + delete fNode; + delete fStreamNode; + + fModelOpener = NULL; + fNode = new BDirectory(*node); + fStreamNode = new AttributeStreamFileNode(fNode); +} + + +void +WindowStateNodeOpener::SetTo(const BEntry *entry, bool forWriting) +{ + delete fModelOpener; + delete fNode; + delete fStreamNode; + + fModelOpener = NULL; + fNode = new BFile(entry, (uint32)(forWriting ? O_RDWR : O_RDONLY)); + fStreamNode = new AttributeStreamFileNode(fNode); +} + + +void +WindowStateNodeOpener::SetTo(Model *model, bool forWriting) +{ + delete fModelOpener; + delete fNode; + delete fStreamNode; + + fNode = NULL; + fStreamNode = NULL; + fModelOpener = new ModelNodeLazyOpener(model, forWriting, false); + if (fModelOpener->IsOpen(forWriting)) + fStreamNode = new AttributeStreamFileNode(fModelOpener->TargetModel()->Node()); +} + + +AttributeStreamNode * +WindowStateNodeOpener::StreamNode() const +{ + return fStreamNode; +} + + +BNode * +WindowStateNodeOpener::Node() const +{ + if (!fStreamNode) + return NULL; + + if (fNode) + return fNode; + + return fModelOpener->TargetModel()->Node(); +} + + +// #pragma mark - + + +BackgroundView::BackgroundView(BRect frame) + : BView(frame, "", B_FOLLOW_ALL, + B_FRAME_EVENTS | B_WILL_DRAW | B_PULSE_NEEDED) +{ +} + + +void +BackgroundView::AttachedToWindow() +{ + SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); +} + + +void +BackgroundView::FrameResized(float, float) +{ + Invalidate(); +} + + +void +BackgroundView::PoseViewFocused(bool) +{ + Invalidate(); +} + + +void +BackgroundView::WindowActivated(bool) +{ + Invalidate(); +} + + +void +BackgroundView::Draw(BRect) +{ + BContainerWindow *window = dynamic_cast(Window()); + if (!window) + return; + + BRect frame(window->PoseView()->Frame()); + + frame.InsetBy(-1, -1); + frame.top -= kTitleViewHeight; + frame.bottom += B_H_SCROLL_BAR_HEIGHT; + frame.right += B_V_SCROLL_BAR_WIDTH; + SetHighColor(100, 100, 100); + StrokeRect(frame); + + // draw the pose view focus + if (window->IsActive() && window->PoseView()->IsFocus()) { + frame.InsetBy(-2, -2); + SetHighColor(keyboard_navigation_color()); + StrokeRect(frame); + } +} + + +void +BackgroundView::Pulse() +{ + BContainerWindow *window = dynamic_cast(Window()); + if (window) + window->PulseTaskLoop(); +} + diff --git a/src/kits/tracker/ContainerWindow.h b/src/kits/tracker/ContainerWindow.h new file mode 100644 index 0000000000..fbd5674c3f --- /dev/null +++ b/src/kits/tracker/ContainerWindow.h @@ -0,0 +1,421 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef _CONTAINER_WINDOW_H +#define _CONTAINER_WINDOW_H + +#include + +#include "LockingList.h" +#include "Model.h" +#include "SlowContextPopup.h" +#include "TaskLoop.h" + +class BPopUpMenu; +class BMenuBar; + +namespace BPrivate { + +class BNavigator; +class BPoseView; +class ModelMenuItem; +class AttributeStreamNode; +class BackgroundImage; +class Model; +class ModelNodeLazyOpener; +class SelectionWindow; + +#define kDefaultFolderTemplate "DefaultFolderTemplate" + +extern const char *kAddOnsMenuName; + +const window_feel kPrivateDesktopWindowFeel = window_feel(1024); +const window_look kPrivateDesktopWindowLook = window_look(4); + // this is a mirror of an app server private values + +enum { + // flags that describe opening of the window + kRestoreWorkspace = 0x1, + kIsHidden = 0x2 + // set when opening a window during initial Tracker start +}; + +class BContainerWindow : public BWindow { + public: + BContainerWindow(LockingList *windowList, + uint32 containerWindowFlags, + window_look look = B_DOCUMENT_WINDOW_LOOK, + window_feel feel = B_NORMAL_WINDOW_FEEL, + uint32 flags = B_WILL_ACCEPT_FIRST_CLICK | B_NO_WORKSPACE_ACTIVATION, + uint32 workspace = B_CURRENT_WORKSPACE); + + virtual ~BContainerWindow(); + + virtual void Init(const BMessage *message = NULL); + + static BRect InitialWindowRect(window_feel); + + virtual void Minimize(bool minimize); + virtual void Quit(); + virtual bool QuitRequested(); + + virtual void UpdateIfTrash(Model *); + + virtual void CreatePoseView(Model *); + + virtual void ShowContextMenu(BPoint, const entry_ref *, BView *); + virtual uint32 ShowDropContextMenu(BPoint); + virtual void MenusBeginning(); + virtual void MenusEnded(); + virtual void MessageReceived(BMessage *); + virtual void FrameResized(float, float); + virtual void FrameMoved(BPoint); + virtual void Zoom(BPoint, float, float); + virtual void WorkspacesChanged(uint32, uint32); + + // virtuals that control setup of window + virtual bool ShouldAddMenus() const; + virtual bool ShouldAddScrollBars() const; + virtual bool ShouldAddCountView() const; + + virtual void CheckScreenIntersect(); + + bool IsTrash() const; + bool InTrash() const; + bool IsPrintersDir() const; + + virtual bool IsShowing(const node_ref *) const; + virtual bool IsShowing(const entry_ref *) const; + + void ResizeToFit(); + + Model *TargetModel() const; + BPoseView *PoseView() const; + BNavigator *Navigator() const; + + virtual void SelectionChanged(); + virtual void ViewModeChanged(uint32 oldMode, uint32 newMode); + + virtual void RestoreState(); + virtual void RestoreState(const BMessage &); + void RestoreStateCommon(); + virtual void SaveState(bool hide = true); + virtual void SaveState(BMessage &) const; + void UpdateTitle(); + + bool StateNeedsSaving() const; + bool SaveStateIsEnabled() const; + void SetSaveStateEnabled(bool); + + void UpdateBackgroundImage(); + + static status_t GetLayoutState(BNode *, BMessage *); + static status_t SetLayoutState(BNode *, const BMessage *); + // calls for inheriting window size, attribute layout, etc. + // deprecated + + virtual void AddMimeTypesToMenu(BMenu *); + void AddMimeTypesToMenu(); + virtual void MarkAttributeMenu(BMenu *); + void MarkAttributeMenu(); + BMenuItem *NewAttributeMenuItem (const char *label, const char *attrName, int32 attrType, + float attrWidth, int32 attrAlign, bool attrEditable, bool attrStatField); + virtual void NewAttributeMenu(BMenu *); + + void HideAttributeMenu(); + void ShowAttributeMenu(); + PiggybackTaskLoop *DelayedTaskLoop(); + // use for RunLater queueing + void PulseTaskLoop(); + // called by some view that has pulse, either BackgroundView or BPoseView + + static bool DefaultStateSourceNode(const char *name, BNode *result, + bool createNew = false, bool createFolder = true); + + // add-on iteration + void EachAddon(bool(*)(const Model *, const char *, uint32 shortcut, bool primary, void *), void *); + + BPopUpMenu *ContextMenu(); + + // drag&drop support + status_t DragStart(const BMessage *); + void DragStop(); + bool Dragging() const; + BMessage *DragMessage() const; + + void ShowSelectionWindow(); + + void ShowNavigator(bool); + void SetSingleWindowBrowseShortcuts(bool); + + void SetPathWatchingEnabled(bool); + bool IsPathWatchingEnabled(void) const; + + protected: + virtual BPoseView *NewPoseView(Model *, BRect, uint32); + // instantiate a different flavor of BPoseView for different + // ContainerWindows + + virtual void RestoreWindowState(AttributeStreamNode *); + virtual void RestoreWindowState(const BMessage &); + virtual void SaveWindowState(AttributeStreamNode *); + virtual void SaveWindowState(BMessage &) const; + + virtual bool NeedsDefaultStateSetup(); + virtual void SetUpDefaultState(); + // these two virtuals control setting up a new folder that + // does not have any state settings yet with the default + + virtual void AddMenus(); + virtual void AddShortcuts(); + // add equivalents of the menu shortcuts to the menuless desktop window + virtual void AddFileMenu(BMenu *menu); + virtual void AddWindowMenu(BMenu *menu); + + virtual void AddContextMenus(); + + virtual void AddFileContextMenus(BMenu *); + virtual void AddWindowContextMenus(BMenu *); + virtual void AddVolumeContextMenus(BMenu *); + virtual void AddDropContextMenus(BMenu *); + + virtual void RepopulateMenus(); + + virtual void SetCutItem(BMenu *); + virtual void SetCopyItem(BMenu *); + virtual void SetPasteItem(BMenu *); + virtual void SetCleanUpItem(BMenu *); + virtual void SetCloseItem(BMenu *); + virtual void SetupNavigationMenu(const entry_ref *, BMenu *); + virtual void SetupMoveCopyMenus(const entry_ref *, BMenu *); + virtual void PopulateMoveCopyNavMenu(BNavMenu *, uint32, const entry_ref *, bool); + + virtual void SetupOpenWithMenu(BMenu *); + virtual void SetUpEditQueryItem(BMenu *); + virtual void SetUpDiskMenu(BMenu *); + + virtual void BuildAddOnMenu(BMenu *); + + enum UpdateMenuContext { + kMenuBarContext, + kPosePopUpContext, + kWindowPopUpContext + }; + + virtual void UpdateMenu(BMenu *menu, UpdateMenuContext context); + + BHandler *ResolveSpecifier(BMessage *, int32, BMessage *, int32, + const char *); + + bool EachAddon(BPath &path, bool(*)(const Model *, const char *, uint32, bool, void *), + BObjectList *, void *); + void LoadAddOn(BMessage *); + + BPopUpMenu *fFileContextMenu; + BPopUpMenu *fWindowContextMenu; + BPopUpMenu *fDropContextMenu; + BPopUpMenu *fVolumeContextMenu; + BSlowContextMenu *fDragContextMenu; + BMenuItem *fMoveToItem; + BMenuItem *fCopyToItem; + BMenuItem *fCreateLinkItem; + BMenuItem *fOpenWithItem; + ModelMenuItem *fNavigationItem; + BMenuBar *fMenuBar; + BNavigator *fNavigator; + BPoseView *fPoseView; + LockingList *fWindowList; + BMenu *fAttrMenu; + BMenu *fWindowMenu; + BMenu *fFileMenu; + + SelectionWindow *fSelectionWindow; + + PiggybackTaskLoop *fTaskLoop; + + bool fIsTrash; + bool fInTrash; + bool fIsPrinters; + + uint32 fContainerWindowFlags; + BackgroundImage *fBackgroundImage; + + private: + BRect fSavedZoomRect; + + static BRect sNewWindRect; + + BPopUpMenu *fContextMenu; + BMessage *fDragMessage; + BObjectList *fCachedTypesList; + bool fWaitingForRefs; + + bool fStateNeedsSaving; + bool fSaveStateIsEnabled; + + bool fIsWatchingPath; + + typedef BWindow _inherited; + + friend int32 show_context_menu(void*); + friend class BackgroundView; +}; + +class WindowStateNodeOpener { + // this class manages opening and closing the proper node for + // state restoring / saving; the constructor knows how to decide wether + // to use a special directory for root, etc. + // setter calls used when no attributes can be read from a node and defaults + // are to be substituted + public: + WindowStateNodeOpener(BContainerWindow *window, bool forWriting); + virtual ~WindowStateNodeOpener(); + + void SetTo(const BDirectory *); + void SetTo(const BEntry *entry, bool forWriting); + void SetTo(Model *, bool forWriting); + + AttributeStreamNode *StreamNode() const; + BNode *Node() const; + + private: + ModelNodeLazyOpener *fModelOpener; + BNode *fNode; + AttributeStreamNode *fStreamNode; +}; + +class BackgroundView : public BView { + // background view placed in a BContainerWindow, under the pose view + public: + BackgroundView(BRect); + virtual void AttachedToWindow(); + virtual void FrameResized(float, float); + virtual void Draw(BRect); + + void PoseViewFocused(bool); + virtual void Pulse(); + + protected: + virtual void WindowActivated(bool); + + private: + typedef BView _inherited; +}; + +int CompareLabels(const BMenuItem *, const BMenuItem *); + +// inlines --------- + +inline BNavigator * +BContainerWindow::Navigator() const +{ + return fNavigator; +} + +inline BPoseView * +BContainerWindow::PoseView() const +{ + return fPoseView; +} + +inline bool +BContainerWindow::IsTrash() const +{ + return fIsTrash; +} + +inline bool +BContainerWindow::InTrash() const +{ + return fInTrash; +} + +inline bool +BContainerWindow::IsPrintersDir() const +{ + return fIsPrinters; +} + +inline void +BContainerWindow::SetUpDiskMenu(BMenu *) +{ + // nothing at this level +} + +inline BPopUpMenu * +BContainerWindow::ContextMenu() +{ + return fContextMenu; +} + +inline bool +BContainerWindow::Dragging() const +{ + return fDragMessage && fCachedTypesList; +} + +inline BMessage * +BContainerWindow::DragMessage() const +{ + return fDragMessage; +} +inline +bool +BContainerWindow::SaveStateIsEnabled() const +{ + return fSaveStateIsEnabled; +} + +inline +void +BContainerWindow::SetSaveStateEnabled(bool value) +{ + fSaveStateIsEnabled = value; +} + +inline +bool +BContainerWindow::IsPathWatchingEnabled() const +{ + return fIsWatchingPath; +} + +filter_result ActivateWindowFilter(BMessage *message, BHandler **target, + BMessageFilter *messageFilter); + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/CountView.cpp b/src/kits/tracker/CountView.cpp new file mode 100644 index 0000000000..56d76211d2 --- /dev/null +++ b/src/kits/tracker/CountView.cpp @@ -0,0 +1,286 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// defines the status area drawn in the bottom left corner of a Tracker window + +#include + +#include "AutoLock.h" +#include "Bitmaps.h" +#include "CountView.h" +#include "ContainerWindow.h" +#include "DirMenu.h" +#include "PoseView.h" + +BCountView::BCountView(BRect bounds, BPoseView* view) + : BView(bounds, "CountVw", B_FOLLOW_LEFT + B_FOLLOW_BOTTOM, + B_PULSE_NEEDED | B_WILL_DRAW), + fLastCount(-1), + fPoseView(view), + fShowingBarberPole(false), + fBarberPoleMap(NULL), + fLastBarberPoleOffset(5), + fStartSpinningAfter(0), + fTypeAheadString("") + +{ + GetTrackerResources()->GetBitmapResource(B_MESSAGE_TYPE, kResBarberPoleBitmap, + &fBarberPoleMap); +} + +BCountView::~BCountView() +{ + delete fBarberPoleMap; +} + +void +BCountView::TrySpinningBarberPole() +{ + if (!fShowingBarberPole) + return; + + if (fStartSpinningAfter && system_time() < fStartSpinningAfter) + return; + + if (fStartSpinningAfter) { + fStartSpinningAfter = 0; + Invalidate(BarberPoleOuterRect()); + } else + Invalidate(BarberPoleInnerRect()); +} + +void +BCountView::Pulse() +{ + TrySpinningBarberPole(); +} + +void +BCountView::EndBarberPole() +{ + if (!fShowingBarberPole) + return; + + fShowingBarberPole = false; + Invalidate(); +} + +const bigtime_t kBarberPoleDelay = 500000; + +void +BCountView::StartBarberPole() +{ + AutoLock lock(Window()); + if (fShowingBarberPole) + return; + + fShowingBarberPole = true; + fStartSpinningAfter = system_time() + kBarberPoleDelay; + // wait a bit before showing the barber pole +} + +BRect +BCountView::BarberPoleInnerRect() const +{ + BRect result = Bounds(); + result.InsetBy(3, 4); + result.left = result.right - 7; + result.bottom = result.top + 7; + return result; +} + +BRect +BCountView::BarberPoleOuterRect() const +{ + BRect result(BarberPoleInnerRect()); + result.InsetBy(-1, -1); + return result; +} + +BRect +BCountView::TextInvalRect() const +{ + BRect result = Bounds(); + result.InsetBy(4, 2); + result.right -= 10; + + return result; +} + +void +BCountView::CheckCount() +{ + // invalidate the count text area if necessary + if (fLastCount != fPoseView->CountItems()) { + fLastCount = fPoseView->CountItems(); + Invalidate(TextInvalRect()); + } + // invalidate barber pole area if necessary + TrySpinningBarberPole(); +} + +void +BCountView::Draw(BRect) +{ + BRect bounds(Bounds()); + BRect barberPoleRect; + BString itemString; + if (!IsTypingAhead()) { + if (fLastCount == 0) + itemString << "no items"; + else if (fLastCount == 1) + itemString << "1 item"; + else + itemString << fLastCount << " items"; + } else + itemString << TypeAhead(); + + + BString string(itemString); + BRect textRect(TextInvalRect()); + + if (fShowingBarberPole && !fStartSpinningAfter) { + barberPoleRect = BarberPoleOuterRect(); + TruncateString(&string, B_TRUNCATE_END, textRect.Width()); + } + + if (IsTypingAhead()) + // use a muted gray for the typeahead + SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_4_TINT)); + else + SetHighColor(0, 0, 0); + MovePenTo(textRect.LeftBottom()); + DrawString(string.String()); + + bounds.top++; + + rgb_color light = tint_color(ViewColor(), B_LIGHTEN_MAX_TINT); + rgb_color shadow = tint_color(ViewColor(), B_DARKEN_2_TINT); + rgb_color lightShadow = tint_color(ViewColor(), B_DARKEN_1_TINT); + + BeginLineArray(fShowingBarberPole && !fStartSpinningAfter ? 9 : 5); + AddLine(bounds.LeftTop(), bounds.RightTop(), light); + AddLine(bounds.LeftTop(), bounds.LeftBottom(), light); + bounds.top--; + + AddLine(bounds.LeftTop(), bounds.RightTop(), shadow); + AddLine(BPoint(bounds.right, bounds.top + 2), bounds.RightBottom(), lightShadow); + AddLine(bounds.LeftBottom(), bounds.RightBottom(), lightShadow); + + if (!fShowingBarberPole || fStartSpinningAfter) { + EndLineArray(); + return; + } + + AddLine(barberPoleRect.LeftTop(), barberPoleRect.RightTop(), shadow); + AddLine(barberPoleRect.LeftTop(), barberPoleRect.LeftBottom(), shadow); + AddLine(barberPoleRect.LeftBottom(), barberPoleRect.RightBottom(), light); + AddLine(barberPoleRect.RightBottom(), barberPoleRect.RightTop(), light); + EndLineArray(); + + barberPoleRect.InsetBy(1, 1); + + BRect destRect(fBarberPoleMap ? fBarberPoleMap->Bounds() : BRect(0, 0, 0, 0)); + destRect.OffsetTo(barberPoleRect.LeftTop() - BPoint(0, fLastBarberPoleOffset)); + fLastBarberPoleOffset -= 1; + if (fLastBarberPoleOffset < 0) + fLastBarberPoleOffset = 5; + + BRegion region; + region.Set(BarberPoleInnerRect()); + ConstrainClippingRegion(®ion); + + if (fBarberPoleMap) + DrawBitmap(fBarberPoleMap, destRect); +} + +void +BCountView::MouseDown(BPoint) +{ + BContainerWindow *window = dynamic_cast(Window()); + window->Activate(); + window->UpdateIfNeeded(); + + if (fPoseView->IsFilePanel() || !fPoseView->TargetModel()) + return; + + if (!window->TargetModel()->IsRoot()) { + BDirMenu *menu = new BDirMenu(NULL, B_REFS_RECEIVED); + BEntry entry; + if (entry.SetTo(window->TargetModel()->EntryRef()) == B_OK) + menu->Populate(&entry, Window(), false, false, true, false, true); + else + menu->Populate(NULL, Window(), false, false, true, false, true); + + menu->SetTargetForItems(be_app); + BPoint pop_pt = Bounds().LeftBottom(); + pop_pt.y += 3; + ConvertToScreen(&pop_pt); + BRect mouse_rect(Bounds()); + ConvertToScreen(&mouse_rect); + menu->Go(pop_pt, true, true, mouse_rect); + delete menu; + } +} + +void +BCountView::AttachedToWindow() +{ + SetFont(be_plain_font); + SetFontSize(9); + + SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + SetLowColor(ViewColor()); + + CheckCount(); +} + +void +BCountView::SetTypeAhead(const char *string) +{ + fTypeAheadString = string; + Invalidate(); +} + +const char * +BCountView::TypeAhead() const +{ + return fTypeAheadString.String(); +} + +bool +BCountView::IsTypingAhead() const +{ + return fTypeAheadString.Length() != 0; +} diff --git a/src/kits/tracker/CountView.h b/src/kits/tracker/CountView.h new file mode 100644 index 0000000000..1822375a90 --- /dev/null +++ b/src/kits/tracker/CountView.h @@ -0,0 +1,84 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef __COUNT_VIEW__ +#define __COUNT_VIEW__ + +#include +#include + +namespace BPrivate { + +class BPoseView; + +class BCountView : public BView { + // displays the item count and a barber pole while the view is updating + +public: + BCountView(BRect, BPoseView *); + ~BCountView(); + + virtual void Draw(BRect); + virtual void MouseDown(BPoint); + virtual void AttachedToWindow(); + virtual void Pulse(); + + void CheckCount(); + void StartBarberPole(); + void EndBarberPole(); + + void SetTypeAhead(const char *); + const char *TypeAhead() const; + bool IsTypingAhead() const; + +private: + BRect BarberPoleInnerRect() const; + BRect BarberPoleOuterRect() const; + BRect TextInvalRect() const; + void TrySpinningBarberPole(); + + int32 fLastCount; + BPoseView *fPoseView; + bool fShowingBarberPole; + BBitmap *fBarberPoleMap; + float fLastBarberPoleOffset; + bigtime_t fStartSpinningAfter; + BString fTypeAheadString; +}; + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/DeskWindow.cpp b/src/kits/tracker/DeskWindow.cpp new file mode 100644 index 0000000000..d852c6bb0c --- /dev/null +++ b/src/kits/tracker/DeskWindow.cpp @@ -0,0 +1,430 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "Attributes.h" +#include "AutoLock.h" +#include "BackgroundImage.h" +#include "Commands.h" +#include "DesktopPoseView.h" +#include "DeskWindow.h" +#include "FSUtils.h" +#include "IconMenuItem.h" +#include "MountMenu.h" +#include "PoseView.h" +#include "Tracker.h" +#include "TemplatesMenu.h" + +#if OPEN_TRACKER +#include "DeviceMap.h" +#else +#include +#endif + +const char *kShelfPath = "tracker_shelf"; + // replicant support + + +BDeskWindow::BDeskWindow(LockingList *windowList) + : BContainerWindow(windowList, 0, + kPrivateDesktopWindowLook, kPrivateDesktopWindowFeel, + B_NOT_MOVABLE | B_WILL_ACCEPT_FIRST_CLICK | + B_NOT_CLOSABLE | B_NOT_MINIMIZABLE | B_ASYNCHRONOUS_CONTROLS, + B_ALL_WORKSPACES), + fDeskShelf(0), + fTrashContextMenu(0), + fShouldUpdateAddonShortcuts(true) +{ +} + + +BDeskWindow::~BDeskWindow() +{ + SaveDesktopPoseLocations(); + // explicit call to SavePoseLocations so that extended pose info + // gets committed properly + PoseView()->DisableSaveLocation(); + // prevent double-saving, this would slow down quitting + PoseView()->StopSettingsWatch(); + stop_watching(this); +} + + +static void +WatchAddOnDir(directory_which dirName, BDeskWindow *window) +{ + BPath path; + if (find_directory(dirName, &path) == B_OK) { + path.Append("Tracker"); + BNode node(path.Path()); + node_ref nodeRef; + node.GetNodeRef(&nodeRef); + TTracker::WatchNode(&nodeRef, B_WATCH_DIRECTORY, window); + } +} + + +void +BDeskWindow::Init(const BMessage *) +{ + AddTrashContextMenu(); + // + // Set the size of the screen before calling the container window's + // Init() because it will add volume poses to this window and + // they will be clipped otherwise + // + BScreen screen(this); + fOldFrame = screen.Frame(); + + PoseView()->SetShowHideSelection(false); + ResizeTo(fOldFrame.Width(), fOldFrame.Height()); + + entry_ref ref; + BPath path; + if (!BootedInSafeMode() && FSFindTrackerSettingsDir(&path) == B_OK) { + path.Append(kShelfPath); + close(open(path.Path(), O_RDONLY | O_CREAT)); + if (get_ref_for_path(path.Path(), &ref) == B_OK) + fDeskShelf = new BShelf(&ref, fPoseView); + if (fDeskShelf) + fDeskShelf->SetDisplaysZombies(true); + } + + // watch add-on directories so that we can track the addons with + // corresponding shortcuts + WatchAddOnDir(B_BEOS_ADDONS_DIRECTORY, this); + WatchAddOnDir(B_USER_ADDONS_DIRECTORY, this); + WatchAddOnDir(B_COMMON_ADDONS_DIRECTORY, this); + + _inherited::Init(); +} + + +struct AddOneShortcutParams { + BDeskWindow *window; + std::set *currentAddonShortcuts; +}; + +static bool +AddOneShortcut(const Model *model, const char *, uint32 shortcut, bool /*primary*/, void *context) +{ + if (!shortcut) + // no shortcut, bail + return false; + + AddOneShortcutParams *params = (AddOneShortcutParams *)context; + BMessage *runAddon = new BMessage(kLoadAddOn); + runAddon->AddRef("refs", model->EntryRef()); + + params->window->AddShortcut(shortcut, B_OPTION_KEY | B_COMMAND_KEY, + runAddon); + params->currentAddonShortcuts->insert(shortcut); + PRINT(("adding new shortcut %c\n", (char)shortcut)); + + return false; +} + + +void +BDeskWindow::MenusBeginning() +{ + _inherited::MenusBeginning(); + + if (fShouldUpdateAddonShortcuts) { + PRINT(("updating addon shortcuts\n")); + fShouldUpdateAddonShortcuts = false; + + // remove all current addon shortcuts + for (std::set::iterator it= fCurrentAddonShortcuts.begin(); + it != fCurrentAddonShortcuts.end(); it++) { + PRINT(("removing shortcut %c\n", *it)); + RemoveShortcut(*it, B_OPTION_KEY | B_COMMAND_KEY); + } + + fCurrentAddonShortcuts.clear(); + + AddOneShortcutParams params; + params.window = this; + params.currentAddonShortcuts = &fCurrentAddonShortcuts; + EachAddon(&AddOneShortcut, ¶ms); + } +} + + +void +BDeskWindow::Quit() +{ + if (fNavigationItem) { + // this duplicates BContainerWindow::Quit because + // fNavigationItem can be part of fTrashContextMenu + // and would get deleted with it + BMenu *menu = fNavigationItem->Menu(); + if (menu) + menu->RemoveItem(fNavigationItem); + delete fNavigationItem; + fNavigationItem = 0; + } + + delete fTrashContextMenu; + fTrashContextMenu = NULL; + + delete fDeskShelf; + _inherited::Quit(); +} + + +BPoseView * +BDeskWindow::NewPoseView(Model *model, BRect rect, uint32 viewMode) +{ + return new DesktopPoseView(model, rect, viewMode); +} + + +void +BDeskWindow::CreatePoseView(Model *model) +{ + fPoseView = NewPoseView(model, Bounds(), kIconMode); + fPoseView->SetIconMapping(false); + fPoseView->SetEnsurePosesVisible(true); + fPoseView->SetAutoScroll(false); + + BScreen screen(this); + rgb_color desktopColor = screen.DesktopColor(); + if (desktopColor.alpha != 255) { + desktopColor.alpha = 255; + screen.SetDesktopColor(desktopColor); + } + + fPoseView->SetViewColor(desktopColor); + fPoseView->SetLowColor(desktopColor); + + AddChild(fPoseView); + + PoseView()->StartSettingsWatch(); +} + + +void +BDeskWindow::AddWindowContextMenus(BMenu *menu) +{ + TemplatesMenu *tempateMenu = new TemplatesMenu(PoseView()); + + menu->AddItem(tempateMenu); + tempateMenu->SetTargetForItems(PoseView()); + tempateMenu->SetFont(be_plain_font); + + menu->AddSeparatorItem(); + menu->AddItem(new BMenuItem("Icon View", new BMessage(kIconMode))); + menu->AddItem(new BMenuItem("Mini Icon View", new BMessage(kMiniIconMode))); + menu->AddSeparatorItem(); + BMenuItem *pasteItem; + menu->AddItem(pasteItem = new BMenuItem("Paste", new BMessage(B_PASTE), 'V')); + menu->AddSeparatorItem(); + menu->AddItem(new BMenuItem("Clean Up", new BMessage(kCleanup), 'K')); + menu->AddItem(new BMenuItem("Select"B_UTF8_ELLIPSIS, + new BMessage(kShowSelectionWindow), 'A', B_SHIFT_KEY)); + menu->AddItem(new BMenuItem("Select All", new BMessage(B_SELECT_ALL), 'A')); + + menu->AddSeparatorItem(); + menu->AddItem(new MountMenu("Mount")); + + menu->AddSeparatorItem(); + menu->AddItem(new BMenu(kAddOnsMenuName)); + + // target items as needed + menu->SetTargetForItems(PoseView()); + pasteItem->SetTarget(this); +} + + +void +BDeskWindow::AddTrashContextMenu() +{ + // setup special trash context menu + fTrashContextMenu = new BPopUpMenu("TrashContext", false, false); + fTrashContextMenu->SetFont(be_plain_font); + fTrashContextMenu->AddItem(new BMenuItem("Empty Trash", + new BMessage(kEmptyTrash))); + fTrashContextMenu->AddItem(new BMenuItem("Open", + new BMessage(kOpenSelection), 'O')); + fTrashContextMenu->AddItem(new BMenuItem("Get Info", new BMessage(kGetInfo), 'I')); + fTrashContextMenu->SetTargetForItems(PoseView()); +} + + +void +BDeskWindow::ShowContextMenu(BPoint loc, const entry_ref *ref, BView *view) +{ + BEntry entry; + + // cleanup previous entries + DeleteSubmenu(fNavigationItem); + + if (ref && entry.SetTo(ref) == B_OK && FSIsTrashDir(&entry)) { + // + // don't show any menu if this is the trash + if (Dragging() && FSIsTrashDir(&entry)) + return; + + // selected item was trash, show the trash context menu instead + BPoint global(loc); + PoseView()->ConvertToScreen(&global); + PoseView()->CommitActivePose(); + BRect mouse_rect(global.x, global.y, global.x, global.y); + mouse_rect.InsetBy(-5, -5); + + EnableNamedMenuItem(fTrashContextMenu, kEmptyTrash, + static_cast(be_app)->TrashFull()); + + SetupNavigationMenu(ref, fTrashContextMenu); + fTrashContextMenu->Go(global, true, false, mouse_rect, true); + } else + _inherited::ShowContextMenu(loc, ref, view); +} + + +void +BDeskWindow::WorkspaceActivated(int32 workspace, bool state) +{ + if (fBackgroundImage) + fBackgroundImage->WorkspaceActivated(PoseView(), workspace, state); +} + + +void +BDeskWindow::SaveDesktopPoseLocations() +{ + PoseView()->SavePoseLocations(&fOldFrame); +} + + +void +BDeskWindow::ScreenChanged(BRect frame, color_space space) +{ + bool frameChanged = (frame != fOldFrame); + + SaveDesktopPoseLocations(); + fOldFrame = frame; + ResizeTo(frame.Width(), frame.Height()); + + if (fBackgroundImage) + fBackgroundImage->ScreenChanged(frame, space); + + PoseView()->CheckPoseVisibility(frameChanged ? &frame : 0); + // if frame changed, pass new frame so that icons can + // get rearranged based on old pose info for the frame +} + + +void +BDeskWindow::UpdateDesktopBackgroundImages() +{ + WindowStateNodeOpener opener(this, false); + fBackgroundImage = BackgroundImage::Refresh(fBackgroundImage, + opener.Node(), true, PoseView()); +} + + +void +BDeskWindow::Show() +{ + if (fBackgroundImage) + fBackgroundImage->Show(PoseView(), current_workspace()); + + PoseView()->CheckPoseVisibility(); + + _inherited::Show(); +} + + +bool +BDeskWindow::ShouldAddScrollBars() const +{ + return false; +} + + +bool +BDeskWindow::ShouldAddMenus() const +{ + return false; +} + + +bool +BDeskWindow::ShouldAddContainerView() const +{ + return false; +} + + +void +BDeskWindow::MessageReceived(BMessage *message) +{ + if (message->WasDropped()) { + const rgb_color *color; + int32 size; + // handle "roColour"-style color drops + if (message->FindData("RGBColor", 'RGBC', + (const void **)&color, &size) == B_OK) { + BScreen(this).SetDesktopColor(*color); + fPoseView->SetViewColor(*color); + fPoseView->SetLowColor(*color); + return; + } + } + + switch (message->what) { + case B_NODE_MONITOR: + PRINT(("will update addon shortcuts\n")); + fShouldUpdateAddonShortcuts = true; + break; + + default: + _inherited::MessageReceived(message); + break; + } +} + diff --git a/src/kits/tracker/DeskWindow.h b/src/kits/tracker/DeskWindow.h new file mode 100644 index 0000000000..9e95dee7e4 --- /dev/null +++ b/src/kits/tracker/DeskWindow.h @@ -0,0 +1,110 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef _DESK_WINDOW_H +#define _DESK_WINDOW_H + +#include +#include + +#include "ContainerWindow.h" +#include "DesktopPoseView.h" + +class BPopUpMenu; + +namespace BPrivate { + +class BDeskWindow : public BContainerWindow { +public: + BDeskWindow(LockingList *windowList); + virtual ~BDeskWindow(); + + virtual void Init(const BMessage *message = NULL); + + virtual void Show(); + virtual void Quit(); + virtual void ScreenChanged(BRect, color_space); + + virtual void ShowContextMenu(BPoint, const entry_ref *, BView *); + virtual void CreatePoseView(Model *); + + virtual bool ShouldAddMenus() const; + virtual bool ShouldAddScrollBars() const; + virtual bool ShouldAddContainerView() const; + + DesktopPoseView *PoseView() const; + + void UpdateDesktopBackgroundImages(); + // Desktop window has special background image handling + + void SaveDesktopPoseLocations(); + +protected: + virtual void AddWindowContextMenus(BMenu *); + void AddTrashContextMenu(); + virtual BPoseView *NewPoseView(Model *, BRect, uint32); + + virtual void WorkspaceActivated(int32, bool); + virtual void MenusBeginning(); + virtual void MessageReceived(BMessage *); + +private: + BShelf *fDeskShelf; + // shelf for replicant support + BPopUpMenu *fTrashContextMenu; + + BRect fOldFrame; + + // in the desktop window addon shortcuts have to be added by AddShortcut + // and we don't always get the MenusBeginning call to check for new addons/update the + // shortcuts -- instead we need to node monitor the addon directory and keep + // a dirty flag that triggers shortcut re-installing + bool fShouldUpdateAddonShortcuts; + std::set fCurrentAddonShortcuts; + // keeps track of which shortcuts are installed for Tracker addons + + typedef BContainerWindow _inherited; +}; + +inline DesktopPoseView * +BDeskWindow::PoseView() const +{ + return dynamic_cast(_inherited::PoseView()); +} + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/DesktopPoseView.cpp b/src/kits/tracker/DesktopPoseView.cpp new file mode 100644 index 0000000000..740d65ad41 --- /dev/null +++ b/src/kits/tracker/DesktopPoseView.cpp @@ -0,0 +1,451 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// DesktopPoseView adds support for displaying integrated desktops +// from multiple volumes to BPoseView +// +// Used by the Desktop window and by the root view in file panels + +#include +#include +#include +#include + +#include "Commands.h" +#include "DesktopPoseView.h" +#include "FSUtils.h" +#include "PoseList.h" +#include "Tracker.h" +#include "TrackerSettings.h" +#include "TrackerString.h" + + +namespace BPrivate { + +bool +ShouldShowDesktopPose(dev_t device, const Model *model, const PoseInfo *) +{ + if (model->NodeRef()->device != device) { + // avoid having more than one Trash + BDirectory remoteTrash; + if (FSGetTrashDir(&remoteTrash, model->NodeRef()->device) == B_OK) { + node_ref remoteTrashNodeRef; + remoteTrash.GetNodeRef(&remoteTrashNodeRef); + if (remoteTrashNodeRef == *model->NodeRef()) + return false; + } + } + return true; +} + +} // namespace BPrivate + + +DesktopEntryListCollection::DesktopEntryListCollection() +{ +} + + +// #pragma mark - + + +DesktopPoseView::DesktopPoseView(Model *model, BRect frame, uint32 viewMode, + uint32 resizeMask) + : BPoseView(model, frame, viewMode, resizeMask) +{ +} + + +EntryListBase * +DesktopPoseView::InitDesktopDirentIterator(BPoseView *nodeMonitoringTarget, + const entry_ref *ref) +{ + // the desktop dirent iterator knows how to iterate over all the volumes, + // integrated onto the desktop + + Model sourceModel(ref, false, true); + if (sourceModel.InitCheck() != B_OK) + return NULL; + + CachedEntryIteratorList *result = new DesktopEntryListCollection(); + + ASSERT(!sourceModel.IsQuery()); + ASSERT(sourceModel.Node()); + BDirectory *sourceDirectory = dynamic_cast(sourceModel.Node()); + + dev_t sourceDevice = sourceModel.NodeRef()->device; + + ASSERT(sourceDirectory); + + // build an iterator list, start with boot + EntryListBase *perDesktopIterator = new CachedDirectoryEntryList( + *sourceDirectory); + + result->AddItem(perDesktopIterator); + if (nodeMonitoringTarget) { + TTracker::WatchNode(sourceModel.NodeRef(), + B_WATCH_DIRECTORY | B_WATCH_NAME | B_WATCH_STAT | B_WATCH_ATTR, + nodeMonitoringTarget); + } + + // add the other volumes + + BVolumeRoster roster; + roster.Rewind(); + BVolume volume; + while (roster.GetNextVolume(&volume) == B_OK) { + if (volume.Device() == sourceDevice) + // got that already + continue; + + if (!DesktopPoseView::ShouldIntegrateDesktop(volume)) + continue; + + BDirectory remoteDesktop; + if (FSGetDeskDir(&remoteDesktop, volume.Device()) < B_OK) + continue; + + BDirectory root; + if (volume.GetRootDirectory(&root) == B_OK) { + perDesktopIterator = new CachedDirectoryEntryList(remoteDesktop); + result->AddItem(perDesktopIterator); + + node_ref nodeRef; + remoteDesktop.GetNodeRef(&nodeRef); + + if (nodeMonitoringTarget) { + TTracker::WatchNode(&nodeRef, + B_WATCH_DIRECTORY | B_WATCH_NAME | B_WATCH_STAT | B_WATCH_ATTR, + nodeMonitoringTarget); + } + } + } + + if (result->Rewind() != B_OK) { + delete result; + if (nodeMonitoringTarget) + nodeMonitoringTarget->HideBarberPole(); + + return NULL; + } + + return result; +} + + +EntryListBase * +DesktopPoseView::InitDirentIterator(const entry_ref *ref) +{ + return InitDesktopDirentIterator(this, ref); +} + + +bool +DesktopPoseView::FSNotification(const BMessage *message) +{ + switch (message->FindInt32("opcode")) { + case B_DEVICE_MOUNTED: + { + dev_t device; + if (message->FindInt32("new device", &device) != B_OK) + break; + + ASSERT(TargetModel()); + TrackerSettings settings; + + BVolume volume(device); + if (volume.InitCheck() != B_OK) + break; + + if (settings.MountVolumesOntoDesktop() + && (!volume.IsShared() || settings.MountSharedVolumesOntoDesktop())) { + // place an icon for the volume onto the desktop + CreateVolumePose(&volume, true); + } + + if (!ShouldIntegrateDesktop(volume)) + break; + + BDirectory otherDesktop; + BEntry entry; + if (FSGetDeskDir(&otherDesktop, volume.Device()) == B_OK + && otherDesktop.GetEntry(&entry) == B_OK) { + // place desktop items from the mounted volume onto the desktop + Model model(&entry); + if (model.InitCheck() == B_OK) + AddPoses(&model); + } + } + break; + } + + return _inherited::FSNotification(message); +} + + +bool +DesktopPoseView::AddPosesThreadValid(const entry_ref *) const +{ + return true; +} + + +bool +DesktopPoseView::ShouldShowPose(const Model *model, const PoseInfo *poseInfo) +{ + ASSERT(TargetModel()); + if (!ShouldShowDesktopPose(TargetModel()->NodeRef()->device, model, poseInfo)) + return false; + + return _inherited::ShouldShowPose(model, poseInfo); +} + + +bool +DesktopPoseView::Represents(const node_ref *ref) const +{ + // When the Tracker is set up to integrate non-boot beos volumes, + // it represents the home/Desktop folders of all beos volumes + + if (TrackerSettings().IntegrateNonBootBeOSDesktops()) { + BDirectory deviceDesktop; + FSGetDeskDir(&deviceDesktop, ref->device); + node_ref nref; + deviceDesktop.GetNodeRef(&nref); + return nref == *ref; + } + + return _inherited::Represents(ref); +} + + +bool +DesktopPoseView::Represents(const entry_ref *ref) const +{ + BEntry entry(ref); + node_ref nref; + entry.GetNodeRef(&nref); + return Represents(&nref); +} + + +void +DesktopPoseView::ShowVolumes(bool visible, bool showShared) +{ + if (LockLooper()) { + if (!visible) + RemoveRootPoses(); + else + AddRootPoses(true, showShared); + UnlockLooper(); + } +} + + +void +DesktopPoseView::RemoveNonBootItems() +{ + AutoLock lock(Window()); + if (!lock) + return; + + EachPoseAndModel(fPoseList, &RemoveNonBootDesktopModels, (BPoseView*)this, (dev_t)0); +} + + +void +DesktopPoseView::AddNonBootItems() +{ + AutoLock lock(Window()); + if (!lock) + return; + + BVolumeRoster volumeRoster; + + BVolume boot; + volumeRoster.GetBootVolume(&boot); + + BVolume volume; + while (volumeRoster.GetNextVolume(&volume) == B_OK) { + if (volume == boot || !ShouldIntegrateDesktop(volume)) + continue; + + BDirectory otherDesktop; + BEntry entry; + + if (FSGetDeskDir(&otherDesktop, volume.Device()) == B_OK + && otherDesktop.GetEntry(&entry) == B_OK) { + // place desktop items from the mounted volume onto the desktop + Model model(&entry); + if (model.InitCheck() == B_OK) + AddPoses(&model); + } + } +} + + +void +DesktopPoseView::StartSettingsWatch() +{ + be_app->LockLooper(); + be_app->StartWatching(this, kShowDisksIconChanged); + be_app->StartWatching(this, kVolumesOnDesktopChanged); + be_app->StartWatching(this, kDesktopIntegrationChanged); + be_app->UnlockLooper(); +} + + +void +DesktopPoseView::StopSettingsWatch() +{ + be_app->LockLooper(); + be_app->StopWatching(this, kShowDisksIconChanged); + be_app->StopWatching(this, kVolumesOnDesktopChanged); + be_app->StopWatching(this, kDesktopIntegrationChanged); + be_app->UnlockLooper(); +} + + +void +DesktopPoseView::AdaptToVolumeChange(BMessage *message) +{ + TTracker *tracker = dynamic_cast(be_app); + if (!tracker) + return; + + bool showDisksIcon = false; + bool mountVolumesOnDesktop = true; + bool mountSharedVolumesOntoDesktop = false; + + message->FindBool("ShowDisksIcon", &showDisksIcon); + message->FindBool("MountVolumesOntoDesktop", &mountVolumesOnDesktop); + message->FindBool("MountSharedVolumesOntoDesktop", &mountSharedVolumesOntoDesktop); + + BEntry entry("/"); + Model model(&entry); + if (model.InitCheck() == B_OK) { + BMessage entryMessage; + entryMessage.what = B_NODE_MONITOR; + + if (showDisksIcon) + entryMessage.AddInt32("opcode", B_ENTRY_CREATED); + else { + entryMessage.AddInt32("opcode", B_ENTRY_REMOVED); + entry_ref ref; + if (entry.GetRef(&ref) == B_OK) { + BContainerWindow *disksWindow = tracker->FindContainerWindow(&ref); + if (disksWindow) { + disksWindow->Lock(); + disksWindow->Close(); + } + } + } + entryMessage.AddInt32("device", model.NodeRef()->device); + entryMessage.AddInt64("node", model.NodeRef()->node); + entryMessage.AddInt64("directory", model.EntryRef()->directory); + entryMessage.AddString("name", model.EntryRef()->name); + BContainerWindow *deskWindow = dynamic_cast(Window()); + if (deskWindow) + deskWindow->PostMessage(&entryMessage, deskWindow->PoseView()); + } + + ShowVolumes(mountVolumesOnDesktop, mountSharedVolumesOntoDesktop); +} + + +void +DesktopPoseView::AdaptToDesktopIntegrationChange(BMessage *message) +{ + bool mountVolumesOnDesktop = true; + bool mountSharedVolumesOntoDesktop = true; + bool integrateNonBootBeOSDesktops = true; + + message->FindBool("MountVolumesOntoDesktop", &mountVolumesOnDesktop); + message->FindBool("MountSharedVolumesOntoDesktop", &mountSharedVolumesOntoDesktop); + message->FindBool("IntegrateNonBootBeOSDesktops", &integrateNonBootBeOSDesktops); + + ShowVolumes(false, mountSharedVolumesOntoDesktop); + ShowVolumes(mountVolumesOnDesktop, mountSharedVolumesOntoDesktop); + + UpdateNonBootDesktopPoses(integrateNonBootBeOSDesktops); +} + + +void +DesktopPoseView::UpdateNonBootDesktopPoses(bool integrateNonBootBeOSDesktops) +{ + static bool nonBootDesktopPosesAlreadyAdded = false; + + BVolumeRoster volumeRoster; + BVolume bootVolume; + volumeRoster.GetBootVolume(&bootVolume); + + dev_t bootDevice = bootVolume.Device(); + + int32 poseCount = CountItems(); + + if (!integrateNonBootBeOSDesktops) { + for (int32 index = 0; index < poseCount; index++) { + Model *model = PoseAtIndex(index)->TargetModel(); + if (!model->IsVolume() && model->NodeRef()->device != bootDevice){ + DeletePose(model->NodeRef()); + index--; + poseCount--; + } + } + nonBootDesktopPosesAlreadyAdded = false; + } else if (!nonBootDesktopPosesAlreadyAdded) { + for (int32 index = 0; index < poseCount; index++) { + Model *model = PoseAtIndex(index)->TargetModel(); + + if (model->IsVolume()) { + BDirectory remoteDesktop; + BEntry entry; + BVolume volume(model->NodeRef()->device); + + if (ShouldIntegrateDesktop(volume) + && FSGetDeskDir(&remoteDesktop, volume.Device()) == B_OK + && remoteDesktop.GetEntry(&entry) == B_OK + && volume != bootVolume) { + // place desktop items from the volume onto the desktop + Model model(&entry); + if (model.InitCheck() == B_OK) + AddPoses(&model); + } + } + } + nonBootDesktopPosesAlreadyAdded = true; + } +} + diff --git a/src/kits/tracker/DesktopPoseView.h b/src/kits/tracker/DesktopPoseView.h new file mode 100644 index 0000000000..cf6dbeb2b4 --- /dev/null +++ b/src/kits/tracker/DesktopPoseView.h @@ -0,0 +1,100 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// DesktopPoseView adds support for displaying integrated desktops +// from multiple volumes to BPoseView + +#ifndef _DESKTOP_POSE_VIEW_H +#define _DESKTOP_POSE_VIEW_H + +#include "EntryIterator.h" +#include "PoseView.h" + +namespace BPrivate { + +class DesktopPoseView : public BPoseView { + // overrides BPoseView to add desktop-view specific code +public: + DesktopPoseView(Model *, BRect, uint32 viewMode, uint32 resizeMask = B_FOLLOW_ALL); + + static EntryListBase *InitDesktopDirentIterator(BPoseView *, const entry_ref *); + + void ShowVolumes(bool visible, bool showShared); + void RemoveNonBootItems(); + void AddNonBootItems(); + + void StartSettingsWatch(); + void StopSettingsWatch(); + + virtual bool AddPosesThreadValid(const entry_ref *) const; + +protected: + virtual EntryListBase *InitDirentIterator(const entry_ref *); + virtual bool FSNotification(const BMessage *); + + virtual bool IsDesktopView() const; + virtual bool ShouldShowPose(const Model *, const PoseInfo *); + + virtual bool Represents(const node_ref *) const; + virtual bool Represents(const entry_ref *) const; + + void AdaptToVolumeChange(BMessage *); + void AdaptToDesktopIntegrationChange(BMessage *); + + void UpdateNonBootDesktopPoses(bool integrateNonBootBeOSDesktops); + +private: + typedef BPoseView _inherited; + + friend bool ShouldShowDesktopPose(dev_t device, const Model *, + const PoseInfo *); +}; + +class DesktopEntryListCollection : public CachedEntryIteratorList { +public: + DesktopEntryListCollection(); +}; + + +inline bool +DesktopPoseView::IsDesktopView() const +{ + return true; +} + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/DeviceMap.h b/src/kits/tracker/DeviceMap.h new file mode 100644 index 0000000000..2b39715666 --- /dev/null +++ b/src/kits/tracker/DeviceMap.h @@ -0,0 +1,423 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +/**************************************************************************** +** WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING ** +** ** +** DANGER, WILL ROBINSON! ** +** ** +** The interfaces contained here are part of BeOS's ** +** ** +** >> PRIVATE NOT FOR PUBLIC USE << ** +** ** +** implementation. ** +** ** +** These interfaces WILL CHANGE in future releases. ** +** If you use them, your app WILL BREAK at some future time. ** +** ** +** (And yes, this does mean that binaries built from OpenTracker will not ** +** be compatible with some future releases of the OS. When that happens, ** +** we will provide an updated version of this file to keep compatibility.) ** +** ** +** WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING ** +****************************************************************************/ + +// volume management utilities +// + +#ifndef __DEVICE_MAP__ +#define __DEVICE_MAP__ + +#if _INCLUDES_CLASS_DEVICE_MAP +#define _DEVICE_MAP_ONLY(x) x +#else +#define _DEVICE_MAP_ONLY(x) +#endif + +#include "DeviceMapGlue.h" + +#include +#include +#include +#include + +class BBitmap; + +enum { + P_UNKNOWN = 0, + P_UNREADABLE, + P_ADD_ON, + P_AUDIO +}; + +enum MountState { + kMounted, + kNotMounted, + kUnknown +}; + +class Device; +class Session; +class Partition; + +typedef bool (Partition::*EachPartitionMemberFunction)(void *); +typedef Partition *(*EachPartitionFunction)(Partition *, void *); +typedef bool (Device::*EachDeviceMemberFunction)(void *); +typedef Device *(*EachDeviceFunction)(Device *, void *); + + +// structure used to communicate between the client application, +// the fs addon and the Initialize call +struct InitializeControlBlock { + BWindow *window; // window is used by the addon to + // center it's dialog over and send the + // result message to + sem_id completionSemaphore; // semaphore used to block the Initialize + // call while waiting for the user to + // interact with the addon dialog and the + // initialization to finish + int32 completionMessage; // the message that will be sent back to + // window upon addon completion + bool cancelOrFail; // the receiving window finds result codes + // from the addon and stuffs them in here + // for the Initialize call to use currently + // only bool is returned by the addon +}; + + +class Partition { +public: + Partition(Session *, const char *name, const char *type, + const char *fsShortName, const char *fsLongName, + const char *volumeName, const char *mountedAt, + uint32 logicalBlockSize, uint64 offset, uint64 blocks, + bool hidden = false); + Partition(Session *, uint32 logicalBlockSize, uint64 offset, + uint64 blocks, bool hidden = false); + Partition(Session *, const partition_data &); + + // getters/setters for partition info + void SetName(const char *); + const char *Name() const; + void SetType(const char *); + const char *Type() const; + void SetFileSystemShortName(const char *); + const char *FileSystemShortName() const; + void SetFileSystemLongName(const char *); + const char *FileSystemLongName() const; + void SetVolumeName(const char *); + const char *VolumeName() const; + void SetMountedAt(const char *); + const char *MountedAt() const; + + status_t GetMountPointNodeRef(node_ref*) const; + void SetMountPointNodeRef(const node_ref*); + + MountState Mounted() const; + void SetMountState(MountState state); + + uint32 LogicalBlockSize() const; + uint64 Offset() const; + uint64 Blocks() const; + bool Hidden() const; + + Session *GetSession() const; + // the session the partition is on + Device *GetDevice() const; + // the device the partition and it's session are on + + bool BuildFileSystemInfo(void *params); + // set up the files system short and long names for this partition + bool RebuildFileSystemInfo(); + // set up the files system short and long names for this partition + + partition_data *Data(); + // accessor to the low level data for low level calls + + bool IsBFSDisk() const; + bool IsHFSDisk() const; + bool IsOFSDisk() const; + + int32 Index() const; + + // utility calls for updating mounting info + bool SetOneUnknownMountState(void *); + bool ClearOneMountState(void *); + + // here is the real stuff + status_t Mount(int32 mountflags = 0, void *params = NULL, int32 len = 0); + status_t Unmount(); + status_t Initialize(InitializeControlBlock *params, + const char *fileSystem = "bfs"); + // the drive setup addon call protocol requires a lot of extra stuff: + // you have to pass a window onto which the drive setup addon will + // center itself. Upon completion it will send it a message with + // signature; the MessageReceived in the window + // needs to release to unblock the call + + int32 UniqueID() const; + // returns a number uinque to the volume in a given device list + // used to identify unmounted volumes + + dev_t VolumeDeviceID() const; + void SetVolumeDeviceID(dev_t); + // only available for mounted volumes + + status_t AddVirtualDevice(); + // publishes a device in /dev... + + void Dump(const char *includeThisText = ""); + +private: + status_t AddVirtualDevice(char *device); + // fills out with the path to the device driver in /dev... + void InitialMountPointName(char *); + + partition_data data; + Session *session; + MountState mounted; + + int32 partitionUniqueID; + dev_t volumeDeviceID; + + node_ref mountPointNodeRef; + status_t mountPointNodeRefStatus; + static int32 lastUniqueID; +}; + + +class Session { +public: + Session(Device *device, const char *, uint64 offset, uint64 blocks, + bool data); + + uint64 Blocks() const; + + void AddPartition(Partition *); + void SetName(const char *); + const char *Name() const; + void SetType(int32 type); + + uint64 Offset() const; + + Device *GetDevice() const; + + int32 CountPartitions() const; + Partition *PartitionAt(int32 index) const; + bool VirtualPartitionOnly() const; + + + void SetAddOnEntry(const BEntry *entry); + // the addon that knows how to handle this session + + bool BuildPartitionMap(int32 dev, uchar *block, bool singlePartition); + // adds one or more partitions to the list + // returns true if multiple partitions found + // pass false in if device cannot support + // multiple partitions (floppy) + + bool BuildFileSystemInfo(int32 dev); + + int32 Index() const; + + bool EachPartition(EachPartitionMemberFunction, void *); + // return true if terminated early + + bool IsDataSession() const; + + static status_t GetSessionData(int32 dev, int32 index, + int32 blockSize, session_data *session); +private: + + + uint64 offset; + uint64 blocks; + char name[B_OS_NAME_LENGTH]; // map name + int32 type; + bool data; + bool virtualPartitionOnly; + BEntry add_on; // we probably don't need this + TypedList partitionList; + Device *device; + +friend class Partition; +friend class Device; +}; + +struct DeviceScanParams { + bigtime_t shortestRescanHartbeat; + bool removableOrUnknownOnly; + bool checkFloppies; + bool checkCDROMs; + bool checkOtherRemovable; +}; + +class Device { +public: + Device(const char *path, int devfd = -1); + ~Device(); + + int32 CountSessions() const; + Session *SessionAt(int32 index) const; + + int32 CountPartitions() const; + + int32 BlockSize() const; + + void SetPartitioningFlags(drive_setup_partition_flags newFlags); + + const char *Name() const; + // device name, including path + const char *DisplayName(bool includeBusID = true, + bool includeLUN = false) const; + + Session *NewSession(int32 dev, int32 index); + + bool FindMountedVolumes(void *); + bool ReadOnly() const + { return readOnly; } + bool Removable() const + { return removable; } + + void UpdateDeviceState(); + status_t Eject(); + + bool NoMedia() const; + + void Dump(const char *); + + bool Dump(void *); + // each function dump + + bool DeviceStateChanged(void *params); + + bool IsFloppy() const; + +private: + void InitNewDeviceState(); + void KillOldDeviceState(); + + bool OneIfDeviceStateChangedAdaptor(void *params); + + void BuildDisplayName(bool includeBusID, bool includeLUN); + + char name[B_FILE_NAME_LENGTH]; + char shortName[B_FILE_NAME_LENGTH]; + int devfd; + drive_setup_partition_flags partitioningFlags; + BBitmap *largeIcon; + BBitmap *miniIcon; + + bool readOnly; + bool removable; + bool isFloppy; + bool media_changed; + bool eject_request; + + int32 blockSize; + + TypedList sessionList; + +friend class Session; +friend class DeviceList; +}; + + +class DeviceList; +class EachPartitionAdaptor; +class EachPartitionMemberAdaptor; +class EachMountablePartitionAdaptor; +class EachInitializablePartitionAdaptor; +class EachMountedPartitionAdaptor; + +template +class EachPartitionIterator { +public: + static ResultType EachPartition(DeviceList *, EachFunction func, + ParamType params); +}; + +class DeviceList : private TypedList { +public: + DeviceList(); + ~DeviceList(); + + status_t RescanDevices(bool runRescanDriver = true); + bool UpdateMountingInfo(); + // returns true if there was a change + + bool EachDevice(EachDeviceMemberFunction, void *); + // return true if terminated early + Device *EachDevice(EachDeviceFunction, void *); + // return true if terminated early + + Partition *EachPartition(EachPartitionFunction func, void *params); + // return Partition * if terminated early + + bool EachPartition(EachPartitionMemberFunction func, void *params); + // return true if terminated early + + Partition *EachMountedPartition(EachPartitionFunction, void *); + Partition *EachMountablePartition(EachPartitionFunction, void *); + Partition *EachInitializablePartition(EachPartitionFunction, void *); + + Partition *PartitionWithID(int32); + + bool CheckDevicesChanged(DeviceScanParams *); + void UpdateChangedDevices(DeviceScanParams *); + bool UnmountDisappearedPartitions(); + // ToDo: pass a hook function for alerting user + +private: + + bool EachChangedDevice(EachDeviceFunction, DeviceScanParams *, void *); + // iterate through every device that is out of sync with current state + // used to sync when media changes, etc. + + status_t ScanDirectory(const char *path); + +friend class EachPartitionIterator; +friend class EachPartitionIterator; +friend class EachPartitionIterator; +friend class EachPartitionIterator; +friend class EachPartitionIterator; +}; + + +#endif diff --git a/src/kits/tracker/DeviceMapGlue.h b/src/kits/tracker/DeviceMapGlue.h new file mode 100644 index 0000000000..63d4da177a --- /dev/null +++ b/src/kits/tracker/DeviceMapGlue.h @@ -0,0 +1,167 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +/**************************************************************************** +** WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING ** +** ** +** DANGER, WILL ROBINSON! ** +** ** +** The interfaces contained here are part of BeOS's ** +** ** +** >> PRIVATE NOT FOR PUBLIC USE << ** +** ** +** implementation. ** +** ** +** These interfaces WILL CHANGE in future releases. ** +** If you use them, your app WILL BREAK at some future time. ** +** ** +** (And yes, this does mean that binaries built from OpenTracker will not ** +** be compatible with some future releases of the OS. When that happens, ** +** we will provide an updated version of this file to keep compatibility.) ** +** ** +** WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING ** +****************************************************************************/ + +#ifndef __INTERNAL_GLUE__ +#define __INTERNAL_GLUE__ + +// This file contains stubs of private headers that were needed to build +// Tracker independently of the BeOS build system. +// Care should be taken to make sure the structures in here are not out of date +// + +#include +#include + + +// cruft from TypedList.h ----------------------- + +class PointerList : public BList { +public: + PointerList(); + virtual ~PointerList(); + + bool Owning() const; +private: + const bool owning; +}; + +template +class TypedList : public PointerList { +public: + virtual ~TypedList(); + + void MakeEmpty(); + bool RemoveItem(T); +}; + +template +TypedList::~TypedList() +{ + if (Owning()) + // have to nuke elements first + MakeEmpty(); +} + +template +bool +TypedList::RemoveItem(T item) +{ + bool result = PointerList::RemoveItem((void *)item); + + if (result && Owning()) + delete item; + + return result; +} + +template +void +TypedList::MakeEmpty() +{ + if (Owning()) { + int32 numElements = CountItems(); + + for (int32 count = 0; count < numElements; count++) + // this is probably not the most efficient, but + // is relatively indepenent of BList implementation + // details + RemoveItem((T)PointerList::LastItem()); + } + PointerList::MakeEmpty(); +} + +// from partition.h ------------------------------ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + uint64 offset; /* in device blocks */ + uint64 blocks; + bool data; /* audio or data session */ +} session_data; + +typedef struct { + char partition_name[B_FILE_NAME_LENGTH]; + char partition_type[B_FILE_NAME_LENGTH]; + char file_system_short_name[B_FILE_NAME_LENGTH]; + char file_system_long_name[B_FILE_NAME_LENGTH]; + char volume_name[B_FILE_NAME_LENGTH]; + char mounted_at[B_FILE_NAME_LENGTH]; + uint32 logical_block_size; + uint64 offset; /* in logical blocks from start of session */ + uint64 blocks; /* in logical blocks */ + bool hidden; /* non-file system partition */ + uchar partition_code; + bool reserved1; + uint32 reserved2; +} partition_data; + +/* Partition add-on entry points */ +/*-------------------------------*/ + +typedef struct { + bool can_partition; + bool can_repartition; +} drive_setup_partition_flags; + + +#ifdef __cplusplus +} +#endif + + +#endif diff --git a/src/kits/tracker/DialogPane.cpp b/src/kits/tracker/DialogPane.cpp new file mode 100644 index 0000000000..7c4323314d --- /dev/null +++ b/src/kits/tracker/DialogPane.cpp @@ -0,0 +1,461 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include "Window.h" +#include "DialogPane.h" +#include "Thread.h" +#include "Utilities.h" + +void +ViewList::RemoveAll(BView *) +{ + EachListItemIgnoreResult(this, &BView::RemoveSelf); +} + +static void +AddSelf(BView *self, BView *to) +{ + to->AddChild(self); +} + +void +ViewList::AddAll(BView *toParent) +{ + EachListItem(this, &AddSelf, toParent); +} + + +DialogPane::DialogPane(BRect mode1Frame, BRect mode2Frame, int32 initialMode, + const char *name, uint32 followFlags, uint32 flags) + : BView(FrameForMode(initialMode, mode1Frame, mode2Frame, mode2Frame), + name, followFlags, flags), + fMode1Frame(mode1Frame), + fMode2Frame(mode2Frame), + fMode3Frame(mode2Frame) +{ + SetMode(initialMode, true); +} + + +DialogPane::DialogPane(BRect mode1Frame, BRect mode2Frame, BRect mode3Frame, + int32 initialMode, const char *name, uint32 followFlags, uint32 flags) + : BView(FrameForMode(initialMode, mode1Frame, mode2Frame, mode3Frame), + name, followFlags, flags), + fMode1Frame(mode1Frame), + fMode2Frame(mode2Frame), + fMode3Frame(mode3Frame) +{ + SetMode(initialMode, true); +} + + +DialogPane::~DialogPane() +{ + fMode3Items.RemoveAll(this); + fMode2Items.RemoveAll(this); +} + + +void +DialogPane::SetMode(int32 mode, bool initialSetup) +{ + ASSERT(mode < 3 && mode >= 0); + + if (!initialSetup && mode == fMode) + return; + + int32 oldMode = fMode; + fMode = mode; + + bool followBottom = (ResizingMode() & B_FOLLOW_BOTTOM) != 0; + // if we are follow bottom, we will move ourselves, need to place us back + float bottomOffset = 0; + if (followBottom) + bottomOffset = Window()->Bounds().bottom - Frame().bottom; + + BRect newBounds(BoundsForMode(fMode)); + if (!initialSetup) + ResizeParentWindow(fMode, oldMode); + + ResizeTo(newBounds.Width(), newBounds.Height()); + + float delta = 0; + if (followBottom) + delta = (Window()->Bounds().bottom - Frame().bottom) - bottomOffset; + + if (delta != 0) { + MoveBy(0, delta); + if (fLatch && (fLatch->ResizingMode() & B_FOLLOW_BOTTOM)) + fLatch->MoveBy(0, delta); + } + + switch (fMode) { + case 0: + { + if (oldMode > 1) + fMode3Items.RemoveAll(this); + if (oldMode > 0) + fMode2Items.RemoveAll(this); + + BView *separator = FindView("separatorLine"); + if (separator) { + BRect frame(separator->Frame()); + frame.InsetBy(-1, -1); + RemoveChild(separator); + Invalidate(); + } + + AddChild(new SeparatorLine(BPoint(newBounds.left, newBounds.top + + newBounds.Height() / 2), newBounds.Width(), false, + "separatorLine")); + } + break; + case 1: + { + if (oldMode > 1) + fMode3Items.RemoveAll(this); + else + fMode2Items.AddAll(this); + + BView *separator = FindView("separatorLine"); + if (separator) { + BRect frame(separator->Frame()); + frame.InsetBy(-1, -1); + RemoveChild(separator); + Invalidate(); + } + } + break; + case 2: + { + fMode3Items.AddAll(this); + if (oldMode < 1) + fMode2Items.AddAll(this); + + BView *separator = FindView("separatorLine"); + if (separator) { + BRect frame(separator->Frame()); + frame.InsetBy(-1, -1); + RemoveChild(separator); + Invalidate(); + } + } + break; + } +} + +void +DialogPane::AttachedToWindow() +{ + BView *parent = Parent(); + if (parent) { + SetViewColor(parent->ViewColor()); + SetLowColor(parent->LowColor()); + } +} + +void +DialogPane::ResizeParentWindow(int32 from, int32 to) +{ + if (!Window()) + return; + + BRect oldBounds = BoundsForMode(from); + BRect newBounds = BoundsForMode(to); + + BPoint by = oldBounds.RightBottom() - newBounds.RightBottom(); + if (by != BPoint(0, 0)) + Window()->ResizeBy(by.x, by.y); +} + +void +DialogPane::AddItem(BView *view, int32 toMode) +{ + if (toMode == 1) + fMode2Items.AddItem(view); + else if (toMode == 2) + fMode3Items.AddItem(view); + if (fMode >= toMode) + AddChild(view); +} + +BRect +DialogPane::FrameForMode(int32 mode) +{ + switch (mode) { + case 0: + return fMode1Frame; + case 1: + return fMode2Frame; + case 2: + return fMode3Frame; + } + return fMode1Frame; +} + +BRect +DialogPane::BoundsForMode(int32 mode) +{ + BRect result; + switch (mode) { + case 0: + result = fMode1Frame; + break; + case 1: + result = fMode2Frame; + break; + case 2: + result = fMode3Frame; + break; + } + result.OffsetTo(0, 0); + return result; +} + +BRect +DialogPane::FrameForMode(int32 mode, BRect mode1Frame, BRect mode2Frame, + BRect mode3Frame) +{ + switch (mode) { + case 0: + return mode1Frame; + case 1: + return mode2Frame; + case 2: + return mode3Frame; + } + return mode1Frame; +} + +const uint32 kValueChanged = 'swch'; + +void +DialogPane::SetSwitch(BControl *control) +{ + fLatch = control; + control->SetMessage(new BMessage(kValueChanged)); + control->SetTarget(this); +} + +void +DialogPane::MessageReceived(BMessage *message) +{ + if (message->what == kValueChanged) { + int32 value; + if (message->FindInt32("be:value", &value) == B_OK) + SetMode(value); + } else + _inherited::MessageReceived(message); +} + +PaneSwitch::PaneSwitch(BRect frame, const char *name, bool leftAligned, + uint32 resizeMask, uint32 flags) + : BControl(frame, name, "", 0, resizeMask, flags), + fLeftAligned(leftAligned), + fPressing(false) +{ +} + +void +PaneSwitch::DoneTracking(BPoint point) +{ + BRect bounds(Bounds()); + bounds.InsetBy(-3, -3); + + fPressing = false; + Invalidate(); + if (bounds.Contains(point)) { + SetValue(!Value()); + Invoke(); + } +} + +void +PaneSwitch::Track(BPoint point, uint32) +{ + BRect bounds(Bounds()); + bounds.InsetBy(-3, -3); + + bool newPressing = bounds.Contains(point); + if (newPressing != fPressing) { + fPressing = newPressing; + Invalidate(); + } +} + + +void +PaneSwitch::MouseDown(BPoint) +{ + if (!IsEnabled()) + return; + + fPressing = true; + MouseDownThread::TrackMouse(this, &PaneSwitch::DoneTracking, + &PaneSwitch::Track); + Invalidate(); +} + + +const rgb_color kNormalColor = {150, 150, 150, 255}; +const rgb_color kHighlightColor = {100, 100, 0, 255}; + +void +PaneSwitch::Draw(BRect) +{ + if (fPressing) + DrawInState(kPressed); + else if (Value()) + DrawInState(kExpanded); + else + DrawInState(kCollapsed); + + + rgb_color markColor = ui_color(B_KEYBOARD_NAVIGATION_COLOR); + + bool focused = IsFocus() && Window()->IsActive(); + BRect bounds(Bounds()); + BeginLineArray(2); + AddLine(BPoint(bounds.left + 2, bounds.bottom - 1), + BPoint(bounds.right - 2, bounds.bottom - 1), focused ? markColor : ViewColor()); + AddLine(BPoint(bounds.left + 2, bounds.bottom), + BPoint(bounds.right - 2, bounds.bottom), focused ? kWhite : ViewColor()); + EndLineArray(); +} + +void +PaneSwitch::DrawInState(PaneSwitch::State state) +{ + BRect rect(0, 0, 10, 10); + + rgb_color outlineColor = {0, 0, 0, 255}; + rgb_color middleColor = state == kPressed ? kHighlightColor : kNormalColor; + + + SetDrawingMode(B_OP_COPY); + + switch (state) { + case kCollapsed: + BeginLineArray(6); + + if (fLeftAligned) { + AddLine(BPoint(rect.left + 3, rect.top + 1), + BPoint(rect.left + 3, rect.bottom - 1), outlineColor); + AddLine(BPoint(rect.left + 3, rect.top + 1), + BPoint(rect.left + 7, rect.top + 5), outlineColor); + AddLine(BPoint(rect.left + 7, rect.top + 5), + BPoint(rect.left + 3, rect.bottom - 1), outlineColor); + + AddLine(BPoint(rect.left + 4, rect.top + 3), + BPoint(rect.left + 4, rect.bottom - 3), middleColor); + AddLine(BPoint(rect.left + 5, rect.top + 4), + BPoint(rect.left + 5, rect.bottom - 4), middleColor); + AddLine(BPoint(rect.left + 5, rect.top + 5), + BPoint(rect.left + 6, rect.top + 5), middleColor); + } else { + AddLine(BPoint(rect.right - 3, rect.top + 1), + BPoint(rect.right - 3, rect.bottom - 1), outlineColor); + AddLine(BPoint(rect.right - 3, rect.top + 1), + BPoint(rect.right - 7, rect.top + 5), outlineColor); + AddLine(BPoint(rect.right - 7, rect.top + 5), + BPoint(rect.right - 3, rect.bottom - 1), outlineColor); + + AddLine(BPoint(rect.right - 4, rect.top + 3), + BPoint(rect.right - 4, rect.bottom - 3), middleColor); + AddLine(BPoint(rect.right - 5, rect.top + 4), + BPoint(rect.right - 5, rect.bottom - 4), middleColor); + AddLine(BPoint(rect.right - 5, rect.top + 5), + BPoint(rect.right - 6, rect.top + 5), middleColor); + } + EndLineArray(); + break; + + case kPressed: + BeginLineArray(7); + if (fLeftAligned) { + AddLine(BPoint(rect.left + 1, rect.top + 7), + BPoint(rect.left + 7, rect.top + 7), outlineColor); + AddLine(BPoint(rect.left + 7, rect.top + 1), + BPoint(rect.left + 7, rect.top + 7), outlineColor); + AddLine(BPoint(rect.left + 1, rect.top + 7), + BPoint(rect.left + 7, rect.top + 1), outlineColor); + + AddLine(BPoint(rect.left + 3, rect.top + 6), + BPoint(rect.left + 6, rect.top + 6), middleColor); + AddLine(BPoint(rect.left + 4, rect.top + 5), + BPoint(rect.left + 6, rect.top + 5), middleColor); + AddLine(BPoint(rect.left + 5, rect.top + 4), + BPoint(rect.left + 6, rect.top + 4), middleColor); + AddLine(BPoint(rect.left + 6, rect.top + 3), + BPoint(rect.left + 6, rect.top + 4), middleColor); + } else { + AddLine(BPoint(rect.right - 1, rect.top + 7), + BPoint(rect.right - 7, rect.top + 7), outlineColor); + AddLine(BPoint(rect.right - 7, rect.top + 1), + BPoint(rect.right - 7, rect.top + 7), outlineColor); + AddLine(BPoint(rect.right - 1, rect.top + 7), + BPoint(rect.right - 7, rect.top + 1), outlineColor); + + AddLine(BPoint(rect.right - 3, rect.top + 6), + BPoint(rect.right - 6, rect.top + 6), middleColor); + AddLine(BPoint(rect.right - 4, rect.top + 5), + BPoint(rect.right - 6, rect.top + 5), middleColor); + AddLine(BPoint(rect.right - 5, rect.top + 4), + BPoint(rect.right - 6, rect.top + 4), middleColor); + AddLine(BPoint(rect.right - 6, rect.top + 3), + BPoint(rect.right - 6, rect.top + 4), middleColor); + } + EndLineArray(); + break; + + case kExpanded: + BeginLineArray(6); + AddLine(BPoint(rect.left + 1, rect.top + 3), + BPoint(rect.right - 1, rect.top + 3), outlineColor); + AddLine(BPoint(rect.left + 1, rect.top + 3), + BPoint(rect.left + 5, rect.top + 7), outlineColor); + AddLine(BPoint(rect.left + 5, rect.top + 7), + BPoint(rect.right - 1, rect.top + 3), outlineColor); + + AddLine(BPoint(rect.left + 3, rect.top + 4), + BPoint(rect.right - 3, rect.top + 4), middleColor); + AddLine(BPoint(rect.left + 4, rect.top + 5), + BPoint(rect.right - 4, rect.top + 5), middleColor); + AddLine(BPoint(rect.left + 5, rect.top + 5), + BPoint(rect.left + 5, rect.top + 6), middleColor); + EndLineArray(); + break; + } +} + diff --git a/src/kits/tracker/DialogPane.h b/src/kits/tracker/DialogPane.h new file mode 100644 index 0000000000..db1b891283 --- /dev/null +++ b/src/kits/tracker/DialogPane.h @@ -0,0 +1,136 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef _DIALOG_PANE_ +#define _DIALOG_PANE_ + +#include + +#include "ObjectList.h" + +namespace BPrivate { + +class ViewList : public BObjectList { +public: + ViewList() + : BObjectList(5, true) + {} + + void RemoveAll(BView *fromParent); + void AddAll(BView *toParent); +}; + +class DialogPane : public BView { + // dialog with collapsible panes +public: + DialogPane(BRect mode1Frame, BRect mode2Frame, int32 initialMode, + const char *name, uint32 followFlags = B_FOLLOW_LEFT | B_FOLLOW_TOP, + uint32 flags = B_WILL_DRAW); + DialogPane(BRect mode1Frame, BRect mode2Frame, BRect mode3Frame, + int32 initialMode, const char *name, + uint32 followFlags = B_FOLLOW_LEFT | B_FOLLOW_TOP, + uint32 flags = B_WILL_DRAW); + + virtual ~DialogPane(); + + BRect FrameForMode(int32); + BRect BoundsForMode(int32); + + int32 Mode() const; + virtual void SetMode(int32, bool initialSetup = false); + + void AddItem(BView *, int32 toMode); + + void SetSwitch(BControl *); + + virtual void AttachedToWindow(); + +protected: + void ResizeParentWindow(int32 from, int32 to); + static BRect FrameForMode(int32, BRect, BRect, BRect); + // called only by the constructor + + virtual void MessageReceived(BMessage *); + +private: + int32 fMode; + + BRect fMode1Frame; + BRect fMode2Frame; + BRect fMode3Frame; + + ViewList fMode2Items; + ViewList fMode3Items; + BControl *fLatch; + + typedef BView _inherited; +}; + +inline int32 +DialogPane::Mode() const +{ + return fMode; +} + +class PaneSwitch : public BControl { + +public: + PaneSwitch(BRect frame, const char *name, bool leftAligned = true, + uint32 resizeMask = B_FOLLOW_LEFT | B_FOLLOW_TOP, + uint32 flags = B_WILL_DRAW | B_NAVIGABLE); + + virtual void Draw(BRect ); + virtual void MouseDown(BPoint ); +protected: + + void DoneTracking(BPoint ); + void Track(BPoint, uint32); + + enum State { + kCollapsed, + kPressed, + kExpanded + }; + + virtual void DrawInState(PaneSwitch::State state); + + bool fLeftAligned; + bool fPressing; +}; + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/DirMenu.cpp b/src/kits/tracker/DirMenu.cpp new file mode 100644 index 0000000000..78c45e9010 --- /dev/null +++ b/src/kits/tracker/DirMenu.cpp @@ -0,0 +1,260 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// ToDo: +// get rid of fMenuBar, SetMenuBar and related mess + +#include +#include +#include +#include +#include +#include + +#include "Attributes.h" +#include "ContainerWindow.h" +#include "DirMenu.h" +#include "FSUtils.h" +#include "IconMenuItem.h" +#include "NavMenu.h" +#include "TrackerSettings.h" +#include "Utilities.h" + + +BDirMenu::BDirMenu(BMenuBar *bar, uint32 command, const char *entryName) + : BPopUpMenu("directories"), + fMenuBar(bar), + fCommand(command) +{ + SetFont(be_plain_font); + if (entryName) + fEntryName = entryName; + else + fEntryName = "refs"; +} + + +BDirMenu::~BDirMenu() +{ +} + + +void +BDirMenu::Populate(const BEntry *startEntry, BWindow *originatingWindow, + bool includeStartEntry, bool select, bool reverse, bool addShortcuts, + bool navMenuEntries) +{ + try { + if (!startEntry) + throw (status_t)B_ERROR; + + Model model(startEntry); + ThrowOnInitCheckError(&model); + + ModelMenuItem *menu = new ModelMenuItem(&model, this, true, true); + + if (fMenuBar) + fMenuBar->AddItem(menu); + + BEntry entry(*startEntry); + + bool showDesktop, showDisksIcon; + { + TrackerSettings settings; + showDesktop = settings.DesktopFilePanelRoot(); + showDisksIcon = settings.ShowDisksIcon(); + } + + // might start one level above startEntry + if (!includeStartEntry) { + BDirectory parent; + BDirectory dir(&entry); + // if we're at the root directory skip "mnt" and go straight to "/" + if (!showDesktop && dir.InitCheck() == B_OK && dir.IsRootDirectory()) + parent.SetTo("/"); + else + entry.GetParent(&parent); + + parent.GetEntry(&entry); + } + + BVolume bootVol; + BVolumeRoster().GetBootVolume(&bootVol); + BDirectory desktopDir; + FSGetDeskDir(&desktopDir, bootVol.Device()); + BEntry desktopEntry; + desktopDir.GetEntry(&desktopEntry); + + for (;;) { + BNode node(&entry); + ThrowOnInitCheckError(&node); + + PoseInfo info; + ReadAttrResult result = ReadAttr(&node, kAttrPoseInfo, + kAttrPoseInfoForeign, B_RAW_TYPE, 0, &info, sizeof(PoseInfo), + &PoseInfo::EndianSwap); + + BDirectory parent; + entry.GetParent(&parent); + + bool hitRoot = false; + + // if we're at the root directory skip "mnt" and go straight to "/" + BDirectory dir(&entry); + if (!showDesktop && dir.InitCheck() == B_OK && dir.IsRootDirectory()) { + hitRoot = true; + parent.SetTo("/"); + } + + if (showDesktop) { + BEntry root("/"); + // warp from "/" to Desktop properly + if (entry == root) { + if (showDisksIcon) + AddDisksIconToMenu(reverse); + entry = desktopEntry; + } + + if (entry == desktopEntry) + hitRoot = true; + } + + if (result == kReadAttrFailed || !info.fInvisible + || (showDesktop && desktopEntry == entry)) + AddItemToDirMenu(&entry, originatingWindow, reverse, + addShortcuts, navMenuEntries); + + if (hitRoot) { + if (!showDesktop && showDisksIcon && *startEntry != "/") + AddDisksIconToMenu(reverse); + break; + } + + parent.GetEntry(&entry); + } + + // select last item in menu + if (!select) + return; + + ModelMenuItem *item = dynamic_cast(ItemAt(CountItems() - 1)); + if (item) { + item->SetMarked(true); + if (menu) { + entry.SetTo(item->TargetModel()->EntryRef()); + ThrowOnError(menu->SetEntry(&entry)); + } + } + } catch (status_t err) { + PRINT(("BDirMenu::Populate: caught error %s\n", strerror(err))); + if (!CountItems()) { + BString error; + error << "Error [" << strerror(err) << "] populating menu"; + AddItem(new BMenuItem(error.String(), 0)); + } + } +} + + +void +BDirMenu::AddItemToDirMenu(const BEntry *entry, BWindow *originatingWindow, + bool atEnd, bool addShortcuts, bool navMenuEntries) +{ + Model model(entry); + if (model.InitCheck() != B_OK) + return; + + BMessage *message = new BMessage(fCommand); + message->AddRef(fEntryName.String(), model.EntryRef()); + + // add reference to the container windows model so that we can + // close the window if + BContainerWindow *window = originatingWindow ? + dynamic_cast(originatingWindow) : 0; + if (window) + message->AddData("nodeRefsToClose", B_RAW_TYPE, window->TargetModel()->NodeRef(), + sizeof (node_ref)); + ModelMenuItem *item; + if (navMenuEntries) { + BNavMenu* subMenu = new BNavMenu(model.Name(), B_REFS_RECEIVED, be_app, window); + entry_ref ref; + entry->GetRef(&ref); + subMenu->SetNavDir(&ref); + item = new ModelMenuItem(&model, subMenu); + item->SetLabel(model.Name()); + item->SetMessage(message); + } else { + item = new ModelMenuItem(&model, model.Name(), message); + } + + if (addShortcuts) { + if (FSIsDeskDir(entry)) + item->SetShortcut('D', B_COMMAND_KEY); + else if (FSIsHomeDir(entry)) + item->SetShortcut('H', B_COMMAND_KEY); + } + + if (atEnd) + AddItem(item); + else + AddItem(item, 0); + + if (fMenuBar) { + ModelMenuItem *menu = dynamic_cast(fMenuBar->ItemAt(0)); + if (menu) { + ThrowOnError(menu->SetEntry(entry)); + item->SetMarked(true); + } + } +} + + +void +BDirMenu::AddDisksIconToMenu(bool atEnd) +{ + BEntry entry("/"); + Model model(&entry); + if (model.InitCheck() != B_OK) + return; + + BMessage *message = new BMessage(fCommand); + message->AddRef(fEntryName.String(), model.EntryRef()); + + ModelMenuItem *item = new ModelMenuItem(&model, "Disks", message); + if (atEnd) + AddItem(item); + else + AddItem(item, 0); +} + diff --git a/src/kits/tracker/DirMenu.h b/src/kits/tracker/DirMenu.h new file mode 100644 index 0000000000..cda6694837 --- /dev/null +++ b/src/kits/tracker/DirMenu.h @@ -0,0 +1,75 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef DIR_MENU_H +#define DIR_MENU_H + +#include +#include + +class MenuBar; + +namespace BPrivate { + +class BDirMenu : public BPopUpMenu { +public: + BDirMenu(BMenuBar *, uint32 command, const char *entryName = 0); + virtual ~BDirMenu(); + + void Populate(const BEntry *startDir, BWindow *originatingWindow, + bool includeStartDir = false, bool select = false, bool reverse = false, + bool addShortcuts = false, bool navMenuEntries = false); + void AddItemToDirMenu(const BEntry *, BWindow *originatingWindow, + bool atEnd, bool addShortcuts, bool navMenuEntries = false); + void AddDisksIconToMenu(bool reverse = false); + + void SetMenuBar(BMenuBar *); + +private: + BMenuBar *fMenuBar; + uint32 fCommand; + BString fEntryName; +}; + +inline void +BDirMenu::SetMenuBar(BMenuBar *bar) +{ + fMenuBar = bar; +} + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/EntryIterator.cpp b/src/kits/tracker/EntryIterator.cpp new file mode 100644 index 0000000000..dcebd6aa0b --- /dev/null +++ b/src/kits/tracker/EntryIterator.cpp @@ -0,0 +1,490 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include +#include +#include + +#include +#include + +#include "EntryIterator.h" +#include "NodeWalker.h" +#include "ObjectList.h" + +TWalkerWrapper::TWalkerWrapper(WALKER_NS::TWalker *walker) + : fWalker(walker), + fStatus(B_OK) +{ +} + +TWalkerWrapper::~TWalkerWrapper() +{ + delete fWalker; +} + +status_t +TWalkerWrapper::InitCheck() const +{ + return fStatus; +} + +status_t +TWalkerWrapper::GetNextEntry(BEntry *entry, bool traverse) +{ + fStatus = fWalker->GetNextEntry(entry, traverse); + return fStatus; +} + +status_t +TWalkerWrapper::GetNextRef(entry_ref *ref) +{ + fStatus = fWalker->GetNextRef(ref); + return fStatus; +} + +int32 +TWalkerWrapper::GetNextDirents(struct dirent *buffer, size_t length, int32 count) +{ + int32 result = fWalker->GetNextDirents(buffer, length, count); + fStatus = result < 0 ? result : (result ? B_OK : B_ENTRY_NOT_FOUND); + return result; +} + +status_t +TWalkerWrapper::Rewind() +{ + return fWalker->Rewind(); +} + +int32 +TWalkerWrapper::CountEntries() +{ + return fWalker->CountEntries(); +} + +EntryListBase::EntryListBase() + : fStatus(B_OK) +{ +} + +status_t +EntryListBase::InitCheck() const +{ + return fStatus; +} + +dirent * +EntryListBase::Next(dirent *ent) +{ + return (dirent *)((char *)ent + ent->d_reclen + sizeof(dirent)); +} + +CachedEntryIterator::CachedEntryIterator(BEntryList *iterator, int32 numEntries, + bool sortInodes) + : fIterator(iterator), + fEntryRefBuffer(NULL), + fCacheSize(numEntries), + fNumEntries(0), + fIndex(0), + fDirentBuffer(NULL), + fCurrentDirent(NULL), + fSortInodes(sortInodes), + fSortedList(NULL), + fEntryBuffer(NULL) +{ +} + + +CachedEntryIterator::~CachedEntryIterator() +{ + delete [] fEntryRefBuffer; + free(fDirentBuffer); + delete fSortedList; + delete [] fEntryBuffer; +} + +status_t +CachedEntryIterator::GetNextEntry(BEntry *result, bool traverse) +{ + ASSERT(!fDirentBuffer); + ASSERT(!fEntryRefBuffer); + + if (!fEntryBuffer) { + fEntryBuffer = new BEntry [fCacheSize]; + ASSERT(fIndex == 0 && fNumEntries == 0); + } + if (fIndex >= fNumEntries) { + // fill up the buffer or stop if error; keep error around + // and return it when appropriate + fStatus = B_OK; + for (fNumEntries = 0; fNumEntries < fCacheSize; fNumEntries++) { + fStatus = fIterator->GetNextEntry(&fEntryBuffer[fNumEntries], + traverse); + if (fStatus != B_OK) + break; + } + fIndex = 0; + } + *result = fEntryBuffer[fIndex++]; + if (fIndex > fNumEntries) + // we are at the end of the cache we loaded up, time to return + // an error, if we had one + return fStatus; + + return B_OK; +} + +status_t +CachedEntryIterator::GetNextRef(entry_ref *ref) +{ + ASSERT(!fDirentBuffer); + ASSERT(!fEntryBuffer); + + if (!fEntryRefBuffer) { + fEntryRefBuffer = new entry_ref[fCacheSize]; + ASSERT(fIndex == 0 && fNumEntries == 0); + } + + if (fIndex >= fNumEntries) { + // fill up the buffer or stop if error; keep error around + // and return it when appropriate + fStatus = B_OK; + for (fNumEntries = 0; fNumEntries < fCacheSize; fNumEntries++) { + fStatus = fIterator->GetNextRef(&fEntryRefBuffer[fNumEntries]); + if (fStatus != B_OK) + break; + } + fIndex = 0; + } + *ref = fEntryRefBuffer[fIndex++]; + if (fIndex > fNumEntries) + // we are at the end of the cache we loaded up, time to return + // an error, if we had one + return fStatus; + + return B_OK; +} + + +static int +CompareInode(const dirent *ent1, const dirent *ent2) +{ + if (ent1->d_ino < ent2->d_ino) + return -1; + else if (ent1->d_ino == ent2->d_ino) + return 0; + else + return 1; +} + +int32 +CachedEntryIterator::GetNextDirents(struct dirent *ent, size_t size, + int32 count) +{ + ASSERT(!fEntryRefBuffer); + if (!fDirentBuffer) { + fDirentBuffer = (dirent *)malloc(kDirentBufferSize); + ASSERT(fIndex == 0 && fNumEntries == 0); + ASSERT(size > sizeof(dirent) + B_FILE_NAME_LENGTH); + } + + if (!count) + return 0; + + if (fIndex >= fNumEntries) { + // we are out of stock, cache em up + fCurrentDirent = fDirentBuffer; + uint32 bufferRemain = kDirentBufferSize; + for (fNumEntries = 0; fNumEntries < fCacheSize; ) { + int32 count = fIterator->GetNextDirents(fCurrentDirent, + bufferRemain, 1); + + if (count <= 0) + break; + + fNumEntries += count; + + int32 currentDirentSize = fCurrentDirent->d_reclen + (ssize_t)sizeof(dirent); + bufferRemain -= currentDirentSize; + if (bufferRemain < (sizeof(dirent) + B_FILE_NAME_LENGTH)) + // cant fit a big entryRef in the buffer, just bail + // and start from scratch + break; + + fCurrentDirent = (dirent *)((char *)fCurrentDirent + currentDirentSize); + } + fCurrentDirent = fDirentBuffer; + if (fSortInodes) { + if (!fSortedList) + fSortedList = new BObjectList(fCacheSize); + else + fSortedList->MakeEmpty(); + + for (int32 count = 0; count < fNumEntries; count++) { + fSortedList->AddItem(fCurrentDirent, 0); + fCurrentDirent = Next(fCurrentDirent); + } + fSortedList->SortItems(CompareInode); + fCurrentDirent = fDirentBuffer; + } + fIndex = 0; + } + if (fIndex >= fNumEntries) + // we are done, no more dirents left + return 0; + + if (fSortInodes) + fCurrentDirent = fSortedList->ItemAt(fIndex); + + fIndex++; + uint32 currentDirentSize = fCurrentDirent->d_reclen + sizeof(dirent); + ASSERT(currentDirentSize <= size); + if (currentDirentSize > size) + return 0; + + memcpy(ent, fCurrentDirent, currentDirentSize); + + if (!fSortInodes) + fCurrentDirent = (dirent *)((char *)fCurrentDirent + currentDirentSize); + + return 1; +} + +status_t +CachedEntryIterator::Rewind() +{ + fIndex = 0; + fNumEntries = 0; + fCurrentDirent = NULL; + fStatus = B_OK; + + delete fSortedList; + fSortedList = NULL; + + return fIterator->Rewind(); +} + +int32 +CachedEntryIterator::CountEntries() +{ + return fIterator->CountEntries(); +} + +void +CachedEntryIterator::SetTo(BEntryList *iterator) +{ + fIndex = 0; + fNumEntries = 0; + fStatus = B_OK; + fIterator = iterator; +} + +CachedDirectoryEntryList::CachedDirectoryEntryList(const BDirectory &dir) + : CachedEntryIterator(0, 40, true), + fDir(dir) +{ + fStatus = fDir.InitCheck(); + SetTo(&fDir); +} + +CachedDirectoryEntryList::~CachedDirectoryEntryList() +{ +} + + +DirectoryEntryList::DirectoryEntryList(const BDirectory &dir) + : fDir(dir) +{ + fStatus = fDir.InitCheck(); +} + +status_t +DirectoryEntryList::GetNextEntry(BEntry *entry, bool traverse) +{ + fStatus = fDir.GetNextEntry(entry, traverse); + return fStatus; +} + +status_t +DirectoryEntryList::GetNextRef(entry_ref *ref) +{ + fStatus = fDir.GetNextRef(ref); + return fStatus; +} + +int32 +DirectoryEntryList::GetNextDirents(struct dirent *buffer, size_t length, + int32 count) +{ + fStatus = fDir.GetNextDirents(buffer, length, count); + return fStatus; +} + +status_t +DirectoryEntryList::Rewind() +{ + fStatus = fDir.Rewind(); + return fStatus; +} + +int32 +DirectoryEntryList::CountEntries() +{ + return fDir.CountEntries(); +} + + +EntryIteratorList::EntryIteratorList() + : fList(5, true), + fCurrentIndex(0) +{ +} + +EntryIteratorList::~EntryIteratorList() +{ + int32 count = fList.CountItems(); + for (;count; count--) { + // workaround for BEntryList not having a proper destructor + BEntryList *entry = fList.RemoveItemAt(count - 1); + EntryListBase *fixedEntry = dynamic_cast(entry); + + if (fixedEntry) + delete fixedEntry; + else + delete entry; + } +} + + +void +EntryIteratorList::AddItem(BEntryList *walker) +{ + fList.AddItem(walker); +} + +status_t +EntryIteratorList::GetNextEntry(BEntry *entry, bool traverse) +{ + for (;;) { + if (fCurrentIndex >= fList.CountItems()) { + fStatus = B_ENTRY_NOT_FOUND; + break; + } + + fStatus = fList.ItemAt(fCurrentIndex)->GetNextEntry(entry, traverse); + if (fStatus != B_ENTRY_NOT_FOUND) + break; + + fCurrentIndex++; + } + return fStatus; +} + +status_t +EntryIteratorList::GetNextRef(entry_ref *ref) +{ + for (;;) { + if (fCurrentIndex >= fList.CountItems()) { + fStatus = B_ENTRY_NOT_FOUND; + break; + } + + fStatus = fList.ItemAt(fCurrentIndex)->GetNextRef(ref); + if (fStatus != B_ENTRY_NOT_FOUND) + break; + + fCurrentIndex++; + } + return fStatus; +} + +int32 +EntryIteratorList::GetNextDirents(struct dirent *buffer, size_t length, int32 count) +{ + int32 result = 0; + for (;;) { + if (fCurrentIndex >= fList.CountItems()) { + fStatus = B_ENTRY_NOT_FOUND; + break; + } + + result = fList.ItemAt(fCurrentIndex)->GetNextDirents(buffer, length, count); + if (result > 0) { + fStatus = B_OK; + break; + } + + fCurrentIndex++; + } + return result; +} + +status_t +EntryIteratorList::Rewind() +{ + fCurrentIndex = 0; + int32 count = fList.CountItems(); + for (int32 index = 0; index < count; index++) + fStatus = fList.ItemAt(index)->Rewind(); + + return fStatus; +} + +int32 +EntryIteratorList::CountEntries() +{ + int32 result = 0; + + int32 count = fList.CountItems(); + for (int32 index = 0; index < count; index++) + result += fList.ItemAt(fCurrentIndex)->CountEntries(); + + return result; +} + + +CachedEntryIteratorList::CachedEntryIteratorList() + : CachedEntryIterator(0, 10, true) +{ + fStatus = B_OK; + SetTo(&fIteratorList); +} + +void +CachedEntryIteratorList::AddItem(BEntryList *walker) +{ + fIteratorList.AddItem(walker); +} + diff --git a/src/kits/tracker/EntryIterator.h b/src/kits/tracker/EntryIterator.h new file mode 100644 index 0000000000..7dd4b86965 --- /dev/null +++ b/src/kits/tracker/EntryIterator.h @@ -0,0 +1,199 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// A lot of the code in here wouldn't be needed if the destructor +// for BEntryList was virtual + +// ToDo: +// get rid of all BEntryList API's in here, replace them with EntryListBase ones + +#ifndef __ENTRY_ITERATOR__ +#define __ENTRY_ITERATOR__ + +#include +#include "ObjectList.h" +#include "NodeWalker.h" + +namespace BPrivate { + +class EntryListBase : public BEntryList { + // this is what BEntryList should have been +public: + EntryListBase(); + virtual ~EntryListBase() {} + + virtual status_t InitCheck() const; + + virtual status_t GetNextEntry(BEntry *entry, bool traverse = false) = 0; + virtual status_t GetNextRef(entry_ref *ref) = 0; + virtual int32 GetNextDirents(struct dirent *buffer, size_t length, + int32 count = INT_MAX) = 0; + + virtual status_t Rewind() = 0; + virtual int32 CountEntries() = 0; + + static dirent *Next(dirent *); + +protected: + status_t fStatus; +}; + +class TWalkerWrapper : public EntryListBase { + // this is to be able to use TWalker polymorfically as BEntryListBase +public: + TWalkerWrapper(WALKER_NS::TWalker *walker); + virtual ~TWalkerWrapper(); + + virtual status_t InitCheck() const; + virtual status_t GetNextEntry(BEntry *entry, bool traverse = false); + virtual status_t GetNextRef(entry_ref *ref); + virtual int32 GetNextDirents(struct dirent *buffer, size_t length, + int32 count = INT_MAX); + virtual status_t Rewind(); + virtual int32 CountEntries(); + +protected: + WALKER_NS::TWalker *fWalker; + status_t fStatus; +}; + +const int32 kDirentBufferSize = 10 * 1024; + +class CachedEntryIterator : public EntryListBase { +public: + // takes any iterator and runs it through a cache of a specified size + // used to cluster entry_ref reads together, away from node accesses + // + // each chunk of iterators in the cache are then returned in an order, + // sorted by their i-node number -- this turns out to give quite a bit + // better performance over just using the order in which they show up using + // the default BEntryList iterator subclass + + CachedEntryIterator(BEntryList *iterator, int32 numEntries, + bool sortInodes = false); + // CachedEntryIterator does not get to own the + virtual ~CachedEntryIterator(); + + virtual status_t GetNextEntry(BEntry *entry, bool traverse = false); + virtual status_t GetNextRef(entry_ref *ref); + virtual int32 GetNextDirents(struct dirent *buffer, size_t length, + int32 count = INT_MAX); + + virtual status_t Rewind(); + virtual int32 CountEntries(); + + virtual void SetTo(BEntryList *iterator); + // CachedEntryIterator does not get to own the + +private: + BEntryList *fIterator; + entry_ref *fEntryRefBuffer; + int32 fCacheSize; + int32 fNumEntries; + int32 fIndex; + + dirent *fDirentBuffer; + dirent *fCurrentDirent; + bool fSortInodes; + BObjectList *fSortedList; + + BEntry *fEntryBuffer; +}; + +class DirectoryEntryList : public EntryListBase { +public: + DirectoryEntryList(const BDirectory &); + + virtual status_t GetNextEntry(BEntry *entry, bool traverse = false); + virtual status_t GetNextRef(entry_ref *ref); + virtual int32 GetNextDirents(struct dirent *buffer, size_t length, + int32 count = INT_MAX); + + virtual status_t Rewind(); + virtual int32 CountEntries(); + +private: + BDirectory fDir; +}; + +class CachedDirectoryEntryList : public CachedEntryIterator { + // this class is to work around not being able to delete + // BEntryList polymorfically - need to have a special + // caching entry list iterator for directories +public: + CachedDirectoryEntryList(const BDirectory &); + virtual ~CachedDirectoryEntryList(); + +private: + BDirectory fDir; +}; + +class EntryIteratorList : public EntryListBase { + // This wraps up several BEntryList style iterators and + // iterates them all, going from one to the other as it finishes + // up each of them +public: + EntryIteratorList(); + virtual ~EntryIteratorList(); + + void AddItem(BEntryList *); + // list gets to own walkers + + virtual status_t GetNextEntry(BEntry *entry, bool traverse = false); + virtual status_t GetNextRef(entry_ref *ref); + virtual int32 GetNextDirents(struct dirent *buffer, size_t length, + int32 count = INT_MAX); + + virtual status_t Rewind(); + virtual int32 CountEntries(); + +protected: + BObjectList fList; + int32 fCurrentIndex; +}; + +class CachedEntryIteratorList : public CachedEntryIterator { +public: + CachedEntryIteratorList(); + void AddItem(BEntryList *); + +protected: + EntryIteratorList fIteratorList; +}; + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/FBCPadding.cpp b/src/kits/tracker/FBCPadding.cpp new file mode 100644 index 0000000000..1773686446 --- /dev/null +++ b/src/kits/tracker/FBCPadding.cpp @@ -0,0 +1,152 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include + +#include "FilePanelPriv.h" +#include "RecentItems.h" + +// FBC fluff, stick it here to not pollute real .cpp files + +void BRecentItemsList::_r1() {} +void BRecentItemsList::_r2() {} +void BRecentItemsList::_r3() {} +void BRecentItemsList::_r4() {} +void BRecentItemsList::_r5() {} +void BRecentItemsList::_r6() {} +void BRecentItemsList::_r7() {} +void BRecentItemsList::_r8() {} +void BRecentItemsList::_r9() {} +void BRecentItemsList::_r10() {} +void BRecentFilesList::_r11() {} +void BRecentFilesList::_r12() {} +void BRecentFilesList::_r13() {} +void BRecentFilesList::_r14() {} +void BRecentFilesList::_r15() {} +void BRecentFilesList::_r16() {} +void BRecentFilesList::_r17() {} +void BRecentFilesList::_r18() {} +void BRecentFilesList::_r19() {} +void BRecentFilesList::_r110() {} +void BRecentFoldersList::_r21() {} +void BRecentFoldersList::_r22() {} +void BRecentFoldersList::_r23() {} +void BRecentFoldersList::_r24() {} +void BRecentFoldersList::_r25() {} +void BRecentFoldersList::_r26() {} +void BRecentFoldersList::_r27() {} +void BRecentFoldersList::_r28() {} +void BRecentFoldersList::_r29() {} +void BRecentFoldersList::_r210() {} +void BRecentAppsList::_r31() {} +void BRecentAppsList::_r32() {} +void BRecentAppsList::_r33() {} +void BRecentAppsList::_r34() {} +void BRecentAppsList::_r35() {} +void BRecentAppsList::_r36() {} +void BRecentAppsList::_r37() {} +void BRecentAppsList::_r38() {} +void BRecentAppsList::_r39() {} +void BRecentAppsList::_r310() {} + +#if !_PR3_COMPATIBLE_ + +void BFilePanel::_ReservedFilePanel1() {} +void BFilePanel::_ReservedFilePanel2() {} +void BFilePanel::_ReservedFilePanel3() {} +void BFilePanel::_ReservedFilePanel4() {} +void BFilePanel::_ReservedFilePanel5() {} +void BFilePanel::_ReservedFilePanel6() {} +void BFilePanel::_ReservedFilePanel7() {} +void BFilePanel::_ReservedFilePanel8() {} + +#endif + +// deprecated cruft + +#if __GNUC__ || __MWERKS__ +extern "C" { + +_EXPORT BFilePanel* +#if __GNUC__ +__10BFilePanel15file_panel_modeP10BMessengerP9entry_refUlbP8BMessageP10BRefFilterT5T5 +#elif __MWERKS__ +__ct__10BFilePanelF15file_panel_modeP10BMessengerP9entry_refUlbP8BMessageP10BRefFilterbb +#endif +(void *self, + file_panel_mode mode, BMessenger *target, + entry_ref *ref, uint32 nodeFlavors, bool multipleSelection, + BMessage *message, BRefFilter *filter, bool modal, + bool hideWhenDone) +{ + return new (self) BFilePanel(mode, target, ref, nodeFlavors, + multipleSelection, message, filter, modal, + hideWhenDone); +} + +_EXPORT void +#if __GNUC__ +SetPanelDirectory__10BFilePanelP10BDirectory +#elif __MWERKS__ +SetPanelDirectory__10BFilePanelFP10BDirectory +#endif +(BFilePanel *self, BDirectory *d) +{ + self->SetPanelDirectory(d); +} + +_EXPORT void +#if __GNUC__ +SetPanelDirectory__10BFilePanelP6BEntry +#elif __MWERKS__ +SetPanelDirectory__10BFilePanelFP6BEntry +#endif +(BFilePanel *self, BEntry *e) +{ + self->SetPanelDirectory(e); +} + +_EXPORT void +#if __GNUC__ +SetPanelDirectory__10BFilePanelP9entry_ref +#elif __MWERKS__ +SetPanelDirectory__10BFilePanelFP9entry_ref +#endif +(BFilePanel *self, entry_ref *r) +{ + self->SetPanelDirectory(r); +} + +} +#endif diff --git a/src/kits/tracker/FSClipboard.cpp b/src/kits/tracker/FSClipboard.cpp new file mode 100644 index 0000000000..c7b559e169 --- /dev/null +++ b/src/kits/tracker/FSClipboard.cpp @@ -0,0 +1,849 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software.. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include "FSClipboard.h" +#include +#include +#include +#include "Commands.h" +#include "FSUtils.h" +#include "Tracker.h" + +// prototypes +static void MakeNodeFromName(node_ref *node, char *name); +static inline void MakeRefName(char *refName, const node_ref *node); +static inline void MakeModeName(char *modeName, const node_ref *node); +static inline void MakeModeNameFromRefName(char *modeName, char *refName); +static inline bool CompareModeAndRefName(const char *modeName, const char *refName); + + +//these are from PoseView.cpp +extern const char *kNoCopyToTrashStr; +extern const char *kNoCopyToRootStr; +extern const char *kOkToMoveStr; + +/* +static bool +FSClipboardCheckIntegrity() +{ + return true; +} +*/ + +bool +FSClipboardHasRefs() +{ + bool result = false; + + if (be_clipboard->Lock()) { + BMessage *clip = be_clipboard->Data(); + if (clip != NULL) { +#ifdef B_BEOS_VERSION_DANO + const +#endif + char *refName; +#ifdef B_BEOS_VERSION_DANO + const +#endif + char *modeName; + uint32 type; + int32 count; + if (clip->GetInfo(B_REF_TYPE, 0, &refName, &type, &count) == B_OK + && clip->GetInfo(B_INT32_TYPE, 0, &modeName, &type, &count) == B_OK) + result = CompareModeAndRefName(modeName, refName); + } + be_clipboard->Unlock(); + } + return result; +} + + +void +FSClipboardStartWatch(BMessenger target) +{ + if (dynamic_cast(be_app) != NULL) + ((TTracker *)be_app)->ClipboardRefsWatcher()->AddToNotifyList(target); + else { + // this code is used by external apps using objects using FSClipboard functions + // i.e: applications using FilePanel + BMessenger messenger(kTrackerSignature); + if (messenger.IsValid()) { + BMessage message(kStartWatchClipboardRefs); + message.AddMessenger("target", target); + messenger.SendMessage(&message); + } + } +} + + +void +FSClipboardStopWatch(BMessenger target) +{ + if (dynamic_cast(be_app) != NULL) + ((TTracker *)be_app)->ClipboardRefsWatcher()->AddToNotifyList(target); + else { + // this code is used by external apps using objects using FSClipboard functions + // i.e: applications using FilePanel + BMessenger messenger(kTrackerSignature); + if (messenger.IsValid()) { + BMessage message(kStopWatchClipboardRefs); + message.AddMessenger("target", target); + messenger.SendMessage(&message); + } + } +} + + +static void +MakeNodeFromName(node_ref *node, char *name) +{ + char *nodeString = strchr(name, '_'); + if (nodeString != NULL) { + node->node = strtoll(nodeString + 1, (char **)NULL, 10); + node->device = atoi(name + 1); + } +} + + +static inline void +MakeRefName(char *refName, const node_ref *node) +{ + sprintf(refName, "r%ld_%Ld", node->device, node->node); +} + + +static inline void +MakeModeName(char *modeName, const node_ref *node) +{ + sprintf(modeName, "m%ld_%Ld", node->device, node->node); +} + + +static inline void +MakeModeName(char *name) +{ + name[0] = 'm'; +} + + +static inline void +MakeModeNameFromRefName(char *modeName, char *refName) +{ + strcpy(modeName, refName); + modeName[0] = 'm'; +} + + +static inline bool +CompareModeAndRefName(const char *modeName, const char *refName) +{ + return !strcmp(refName + 1, modeName + 1); +} + + +void +FSClipboardClear() +{ + if (!be_clipboard->Lock()) + return; + + be_clipboard->Clear(); + be_clipboard->Commit(); + be_clipboard->Unlock(); +} + + +/** This function adds the given poses list to the clipboard, for both copy + * and cut. All poses in the list must have "directory" as parent. + * "moveMode" is either kMoveSelection or kCopySelection. + * It will check if the entries are already present, so that there can only + * be one reference to them in the clipboard. + */ + +uint32 +FSClipboardAddPoses(const node_ref *directory, PoseList *list, uint32 moveMode, + bool clearClipboard) +{ + uint32 refsAdded = 0; + int32 listCount = list->CountItems(); + + if (listCount == 0 || !be_clipboard->Lock()) + return 0; + + // update message to be send to all listeners + BMessage updateMessage(kFSClipboardChanges); + updateMessage.AddInt32("device", directory->device); + updateMessage.AddInt64("directory", directory->node); + updateMessage.AddBool("clearClipboard", clearClipboard); + + TClipboardNodeRef clipNode; + clipNode.moveMode = moveMode; + + if (clearClipboard) + be_clipboard->Clear(); + + BMessage *clip = be_clipboard->Data(); + if (clip != NULL) { + for (int32 index = 0; index < listCount; index++) { + char refName[64], modeName[64]; + BPose *pose = (BPose *)list->ItemAt(index); + Model *model = pose->TargetModel(); + const node_ref *node = model->NodeRef(); + + BEntry entry; + model->GetEntry(&entry); + if (model->IsVolume() + || model->IsRoot() + || FSIsTrashDir(&entry) + || FSIsDeskDir(&entry)) + continue; + + MakeRefName(refName, node); + MakeModeNameFromRefName(modeName, refName); + + if (clearClipboard) { + if (clip->AddInt32(modeName, (int32)moveMode) == B_OK) + if (clip->AddRef(refName, model->EntryRef()) == B_OK) { + pose->SetClipboardMode(moveMode); + + clipNode.node = *node; + updateMessage.AddData("tcnode", T_CLIPBOARD_NODE, &clipNode, + sizeof(TClipboardNodeRef), true, listCount); + + refsAdded++; + } else + clip->RemoveName(modeName); + } else { + if (clip->ReplaceInt32(modeName, (int32)moveMode) == B_OK) { + // replace old mode if entry already exists in clipboard + if (clip->ReplaceRef(refName, model->EntryRef()) == B_OK) { + pose->SetClipboardMode(moveMode); + + clipNode.node = *node; + updateMessage.AddData("tcnode", T_CLIPBOARD_NODE, &clipNode, + sizeof(TClipboardNodeRef), true, listCount); + + refsAdded++; + } else { + clip->RemoveName(modeName); + + clipNode.node = *node; + clipNode.moveMode = kDelete; // note removing node + updateMessage.AddData("tcnode", T_CLIPBOARD_NODE, &clipNode, + sizeof(TClipboardNodeRef), true, listCount); + clipNode.moveMode = moveMode; // set it back to current value + } + } else { + // add if it doesn't exist + if (clip->AddRef(refName, model->EntryRef()) == B_OK + && clip->AddInt32(modeName, (int32)moveMode) == B_OK) { + pose->SetClipboardMode(moveMode); + + clipNode.node = *node; + updateMessage.AddData("tcnode", T_CLIPBOARD_NODE, &clipNode, + sizeof(TClipboardNodeRef), true, listCount); + + refsAdded++; + } else { + clip->RemoveName(modeName); + clip->RemoveName(refName); + // here notifying delete isn't needed as node didn't exist in clipboard + } + } + } + } + be_clipboard->Commit(); + } + be_clipboard->Unlock(); + + BMessenger(kTrackerSignature).SendMessage(&updateMessage); + // Tracker will notify all listeners + + return refsAdded; +} + + +uint32 +FSClipboardRemovePoses(const node_ref *directory, PoseList *list) +{ + + if (!be_clipboard->Lock()) + return 0; + + // update message to be send to all listeners + BMessage updateMessage(kFSClipboardChanges); + updateMessage.AddInt32("device", directory->device); + updateMessage.AddInt64("directory", directory->node); + updateMessage.AddBool("clearClipboard", false); + + TClipboardNodeRef clipNode; + clipNode.moveMode = kDelete; + + uint32 refsRemoved = 0; + + BMessage *clip = be_clipboard->Data(); + if (clip != NULL) { + int32 listCount = list->CountItems(); + + for (int32 index = 0; index < listCount; index++) { + char refName[64], modeName[64]; + BPose *pose = (BPose *)list->ItemAt(index); + + clipNode.node = *pose->TargetModel()->NodeRef(); + MakeRefName(refName, &clipNode.node); + MakeModeName(modeName); + + if (clip->RemoveName(refName) == B_OK && clip->RemoveName(modeName)) { + updateMessage.AddData("tcnode", T_CLIPBOARD_NODE, &clipNode, + sizeof(TClipboardNodeRef), true, listCount); + refsRemoved++; + } + } + be_clipboard->Commit(); + } + be_clipboard->Unlock(); + + BMessenger(kTrackerSignature).SendMessage(&updateMessage); + // Tracker will notify all listeners + + return refsRemoved; +} + + +/** Pastes entries from the clipboard to the target model's directory. + * Updates moveModes and notifies listeners if necessary. + */ + +bool +FSClipboardPaste(Model *model, uint32 linksMode) +{ + if (!FSClipboardHasRefs()) + return false; + + BMessenger tracker(kTrackerSignature); + + node_ref *destNodeRef = (node_ref *)model->NodeRef(); + + // these will be passed to the asynchronous copy/move process + BObjectList *moveList = new BObjectList(0, true); + BObjectList *copyList = new BObjectList(0, true); + + if ((be_clipboard->Lock())) { + BMessage *clip = be_clipboard->Data(); + if (clip != NULL) { + char modeName[64]; + uint32 moveMode = 0; + + BMessage *updateMessage = NULL; + node_ref updateNodeRef; + updateNodeRef.device = -1; + + char *refName; + type_code type; + int32 count; + for (int32 index = 0; clip->GetInfo(B_REF_TYPE, index, +#ifdef B_BEOS_VERSION_DANO + (const char **) +#endif + &refName, &type, &count) == B_OK; index++) { + entry_ref ref; + if (clip->FindRef(refName, &ref) != B_OK) + continue; + + // If the entry_ref's directory has changed, send previous notification + // (if any), and start new one for the new directory + if (updateNodeRef.device != ref.device + || updateNodeRef.node != ref.directory) { + if (updateMessage != NULL) { + tracker.SendMessage(updateMessage); + delete updateMessage; + } + + updateNodeRef.device = ref.device; + updateNodeRef.node = ref.directory; + + updateMessage = new BMessage(kFSClipboardChanges); + updateMessage->AddInt32("device", updateNodeRef.device); + updateMessage->AddInt64("directory", updateNodeRef.node); + } + + // we need this data later on + MakeModeNameFromRefName(modeName, refName); + if (!linksMode && clip->FindInt32(modeName, (int32 *)&moveMode) != B_OK) + continue; + + BEntry entry(&ref); + + uint32 newMoveMode = 0; + bool sameDirectory = destNodeRef->device == ref.device && destNodeRef->node == ref.directory; + + if (!entry.Exists()) { + // The entry doesn't exist anymore, so we'll remove + // that entry from the clipboard as well + clip->RemoveName(refName); + clip->RemoveName(modeName); + + newMoveMode = kDelete; + } else { + // the entry does exist, so lets see what we will + // do with it + if (!sameDirectory) { + if (linksMode || moveMode == kMoveSelectionTo) { + // the linksMode uses the moveList as well + moveList->AddItem(new entry_ref(ref)); + } else if (moveMode == kCopySelectionTo) + copyList->AddItem(new entry_ref(ref)); + } + + // if the entry should have been removed from its directory, + // we want to copy that entry next time, no matter if the + // items don't have to be moved at all (source == target) + if (moveMode == kMoveSelectionTo) + newMoveMode = kCopySelectionTo; + } + + // add the change to the update message (if necessary) + if (newMoveMode) { + clip->ReplaceInt32(modeName, kCopySelectionTo); + + TClipboardNodeRef clipNode; + MakeNodeFromName(&clipNode.node, modeName); + clipNode.moveMode = kDelete; + updateMessage->AddData("tcnode", T_CLIPBOARD_NODE, &clipNode, + sizeof(TClipboardNodeRef), true); + } + } + be_clipboard->Commit(); + + // send notification for the last directory + if (updateMessage != NULL) { + tracker.SendMessage(updateMessage); + delete updateMessage; + } + } + be_clipboard->Unlock(); + } + + bool okToMove = true; + + // can't copy/paste to root('/') directory + if (model->IsRoot()) { + (new BAlert("", kNoCopyToRootStr, "Cancel", NULL, NULL, + B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); + okToMove = false; + } + + BEntry entry; + model->GetEntry(&entry); + + // can't copy items into the trash + if (copyList->CountItems() > 0 && FSIsTrashDir(&entry)) { + (new BAlert("", kNoCopyToTrashStr, "Cancel", NULL, NULL, + B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); + okToMove = false; + } + + if (!okToMove) { + // there was some problem with our target, so we bail out here + delete moveList; + delete copyList; + return false; + } + + // asynchronous calls take over ownership of the objects passed to it + if (moveList->CountItems() > 0) + FSMoveToFolder(moveList, new BEntry(entry), linksMode ? linksMode : kMoveSelectionTo); + else + delete moveList; + + if (copyList->CountItems() > 0) + FSMoveToFolder(copyList, new BEntry(entry), kCopySelectionTo); + else + delete copyList; + + return true; +} + + +/** Seek node in clipboard, if found return it's moveMode + * else return 0 + */ + +uint32 +FSClipboardFindNodeMode(Model *model, bool updateRefIfNeeded) +{ + int32 moveMode = 0; + + if (be_clipboard->Lock()) { + bool remove = false; + bool change = false; + + BMessage *clip = be_clipboard->Data(); + if (clip != NULL) { + const node_ref *node = model->NodeRef(); + char modeName[64]; + MakeModeName(modeName, node); + if ((clip->FindInt32(modeName, &moveMode) == B_OK)) { + const entry_ref *ref = model->EntryRef(); + entry_ref clipref; + char refName[64]; + MakeRefName(refName, node); + if ((clip->FindRef(refName, &clipref) == B_OK)) { + if (clipref != *ref) { + if (updateRefIfNeeded) { + clip->ReplaceRef(refName, ref); + change = true; + } else { + clip->RemoveName(refName); + clip->RemoveName(modeName); + change = true; + remove = true; + moveMode = 0; + } + } + } else { + clip->RemoveName(modeName); + change = true; + remove = true; + moveMode = 0; + } + } + } + if (change) + be_clipboard->Commit(); + + be_clipboard->Unlock(); + + if (remove) + FSClipboardRemove(model); + } + + return (uint32)moveMode; +} + + +void +FSClipboardRemove(Model *model) +{ + BMessenger messenger(kTrackerSignature); + if (messenger.IsValid()) { + BMessage *report = new BMessage(kFSClipboardChanges); + TClipboardNodeRef tcnode; + tcnode.node = *model->NodeRef(); + tcnode.moveMode = kDelete; + const entry_ref *ref = model->EntryRef(); + report->AddInt32("device", ref->device); + report->AddInt64("directory", ref->directory); + report->AddBool("clearClipboard", false); + report->AddData("tcnode", T_CLIPBOARD_NODE, &tcnode, sizeof(tcnode), true); + messenger.SendMessage(report); + delete report; + } +} + + +// #pragma mark - + + +BClipboardRefsWatcher::BClipboardRefsWatcher() + : BLooper("ClipboardRefsWatcher", B_LOW_PRIORITY, 4096), + fNotifyList(10, false) +{ + watch_node(NULL, B_WATCH_MOUNT, this); + fRefsInClipboard = FSClipboardHasRefs(); + be_clipboard->StartWatching(this); +} + + +BClipboardRefsWatcher::~BClipboardRefsWatcher() +{ + stop_watching(this); + be_clipboard->StopWatching(this); +} + + +void +BClipboardRefsWatcher::AddToNotifyList(BMessenger target) +{ + if (Lock()) { + // add the messenger if it's not already in the list + // ToDo: why do we have to care about that? + BMessenger *messenger; + bool found = false; + + for (int32 index = 0;(messenger = fNotifyList.ItemAt(index)) != NULL; index++) { + if (*messenger == target) { + found = true; + break; + } + } + if (!found) + fNotifyList.AddItem(new BMessenger(target)); + + Unlock(); + } +} + + +void +BClipboardRefsWatcher::RemoveFromNotifyList(BMessenger target) +{ + if (Lock()) { + BMessenger *messenger; + + for (int32 index = 0;(messenger = fNotifyList.ItemAt(index)) != NULL; index++) { + if (*messenger == target) { + delete fNotifyList.RemoveItemAt(index); + break; + } + } + Unlock(); + } +} + + +void +BClipboardRefsWatcher::AddNode(const node_ref *node) +{ + TTracker::WatchNode(node, B_WATCH_NAME, this); + fRefsInClipboard = true; +} + + +void +BClipboardRefsWatcher::RemoveNode(node_ref *node, bool removeFromClipboard) +{ + watch_node(node, B_STOP_WATCHING, this); + + if (!removeFromClipboard) + return; + + if (be_clipboard->Lock()) { + BMessage *clip = be_clipboard->Data(); + if (clip != NULL) { + char name[64]; + MakeRefName(name, node); + clip->RemoveName(name); + MakeModeName(name); + clip->RemoveName(name); + + be_clipboard->Commit(); + } + be_clipboard->Unlock(); + } +} + + +void +BClipboardRefsWatcher::RemoveNodesByDevice(dev_t device) +{ + if (!be_clipboard->Lock()) + return; + + BMessage *clip = be_clipboard->Data(); + if (clip != NULL) { + char deviceName[6]; + sprintf(deviceName, "r%ld_", device); + + int32 index = 0; + char *refName; + type_code type; + int32 count; + while (clip->GetInfo(B_REF_TYPE, index, +#ifdef B_BEOS_VERSION_DANO + (const char **) +#endif + &refName, &type, &count) == B_OK) { + if (!strncmp(deviceName, refName, strlen(deviceName))) { + clip->RemoveName(refName); + MakeModeName(refName); + clip->RemoveName(refName); + + node_ref node; + MakeNodeFromName(&node, refName); + watch_node(&node, B_STOP_WATCHING, this); + } + index++; + } + be_clipboard->Commit(); + } + be_clipboard->Unlock(); +} + + +void +BClipboardRefsWatcher::UpdateNode(node_ref *node, entry_ref *ref) +{ + if (!be_clipboard->Lock()) + return; + + BMessage *clip = be_clipboard->Data(); + if (clip != NULL) { + char name[64]; + MakeRefName(name, node); + if ((clip->ReplaceRef(name, ref)) != B_OK) { + clip->RemoveName(name); + MakeModeName(name); + clip->RemoveName(name); + + RemoveNode(node); + } + be_clipboard->Commit(); + } + be_clipboard->Unlock(); +} + + +void +BClipboardRefsWatcher::Clear() +{ + stop_watching(this); + watch_node(NULL, B_WATCH_MOUNT, this); + + BMessage message(kFSClipboardChanges); + message.AddBool("clearClipboard", true); + if (Lock()) { + int32 items = fNotifyList.CountItems(); + for (int32 i = 0;i < items;i++) { + fNotifyList.ItemAt(i)->SendMessage(&message); + } + Unlock(); + } +} + +/* +void +BClipboardRefsWatcher::UpdatePoseViews(bool clearClipboard, const node_ref *node) +{ + BMessage message(kFSClipboardChanges); + message.AddInt32("device", node->device); + message.AddInt64("directory", node->node); + message.AddBool("clearClipboard", clearClipboard); + + if (Lock()) { + int32 items = fNotifyList.CountItems(); + for (int32 i = 0;i < items;i++) { + fNotifyList.ItemAt(i)->SendMessage(&message); + } + Unlock(); + } +} +*/ + +void +BClipboardRefsWatcher::UpdatePoseViews(BMessage *reportMessage) +{ + if (Lock()) { + // check if it was cleared, if so clear watching + bool clearClipboard = false; + if (reportMessage->FindBool("clearClipboard", &clearClipboard) == B_OK + && clearClipboard) { + stop_watching(this); + watch_node(NULL, B_WATCH_MOUNT, this); + } + + // loop through reported node_ref's movemodes: + // move or copy: start watching node_ref + // remove: stop watching node_ref + int32 index = 0; + TClipboardNodeRef *tcnode = NULL; + ssize_t size; + while (reportMessage->FindData("tcnode", T_CLIPBOARD_NODE, index, (const void**)&tcnode, &size) == B_OK) { + if (tcnode->moveMode == kDelete) { + watch_node(&tcnode->node, B_STOP_WATCHING, this); + } else { + watch_node(&tcnode->node, B_STOP_WATCHING, this); + TTracker::WatchNode(&tcnode->node, B_WATCH_NAME, this); + fRefsInClipboard = true; + } + index++; + } + + // send report + int32 items = fNotifyList.CountItems(); + for (int32 i = 0;i < items;i++) { + fNotifyList.ItemAt(i)->SendMessage(reportMessage); + } + Unlock(); + } +} + + +void +BClipboardRefsWatcher::MessageReceived(BMessage *message) +{ + if (message->what == B_CLIPBOARD_CHANGED && fRefsInClipboard) { + if (!(fRefsInClipboard = FSClipboardHasRefs())) + Clear(); + return; + } else if (message->what != B_NODE_MONITOR) { + _inherited::MessageReceived(message); + return; + } + + switch (message->FindInt32("opcode")) { + case B_ENTRY_MOVED: + { + ino_t toDir; + ino_t fromDir; + node_ref node; + const char *name = NULL; + message->FindInt64("from directory", &fromDir); + message->FindInt64("to directory", &toDir); + message->FindInt64("node", &node.node); + message->FindInt32("device", &node.device); + message->FindString("name", &name); + entry_ref ref(node.device, toDir, name); + UpdateNode(&node, &ref); + break; + } + + case B_DEVICE_UNMOUNTED: + { + dev_t device; + message->FindInt32("device", &device); + RemoveNodesByDevice(device); + break; + } + + case B_ENTRY_REMOVED: + { + node_ref node; + message->FindInt64("node", &node.node); + message->FindInt32("device", &node.device); + RemoveNode(&node, true); + break; + } + } +} diff --git a/src/kits/tracker/FSClipboard.h b/src/kits/tracker/FSClipboard.h new file mode 100644 index 0000000000..9dd85c0949 --- /dev/null +++ b/src/kits/tracker/FSClipboard.h @@ -0,0 +1,95 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software.. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef FS_CLIPBOARD_H +#define FS_CLIPBOARD_H + +#include +#include "Model.h" +#include "ObjectList.h" +#include "Pose.h" +#include "PoseView.h" + + +namespace BPrivate { + +typedef struct { + node_ref node; + uint32 moveMode; +} TClipboardNodeRef; +const int32 T_CLIPBOARD_NODE = 'TCNR'; + +class BClipboardRefsWatcher : public BLooper { + public: + BClipboardRefsWatcher(); + virtual ~BClipboardRefsWatcher(); + + void AddToNotifyList(BMessenger target); + void RemoveFromNotifyList(BMessenger target); + void AddNode(const node_ref *node); + void RemoveNode(node_ref *node, bool removeFromClipboard = false); + void RemoveNodesByDevice(dev_t device); + void UpdateNode(node_ref *node, entry_ref *ref); + void Clear(); +// void UpdatePoseViews(bool clearClipboard, const node_ref *node); + void UpdatePoseViews(BMessage *reportMessage); + + protected: + virtual void MessageReceived(BMessage *); + + private: + bool fRefsInClipboard; + BObjectList fNotifyList; + + typedef BLooper _inherited; +}; + +} // namespace BPrivate + +using namespace BPrivate; + +//bool FSClipboardCheckIntegrity(); +bool FSClipboardHasRefs(); + +void FSClipboardStartWatch(BMessenger target); +void FSClipboardStopWatch(BMessenger target); + +void FSClipboardClear(); +uint32 FSClipboardAddPoses(const node_ref *directory, PoseList *list, uint32 moveMode, bool clearClipboard); +uint32 FSClipboardRemovePoses(const node_ref *directory, PoseList *list); +bool FSClipboardPaste(Model *model, uint32 linksMode = 0); +void FSClipboardRemove(Model *model); +uint32 FSClipboardFindNodeMode(Model *model, bool updateRefIfNeeded); + +#endif /* FS_CLIPBOARD_H */ diff --git a/src/kits/tracker/FSUndoRedo.cpp b/src/kits/tracker/FSUndoRedo.cpp new file mode 100644 index 0000000000..a3fe28e9e8 --- /dev/null +++ b/src/kits/tracker/FSUndoRedo.cpp @@ -0,0 +1,459 @@ +#include "Commands.h" +#include "FSUndoRedo.h" +#include "FSUtils.h" + +#include +#include +#include +#include + + +static const int32 kUndoRedoListMaxCount = 20; + + +namespace BPrivate { + +class UndoItem { + public: + virtual ~UndoItem() {} + + virtual status_t Undo() = 0; + virtual status_t Redo() = 0; + + virtual void UpdateEntry(BEntry */*entry*/, const char */*name*/) {} + // updates the name of the target from the source entry "entry" +}; + +static BObjectList sUndoList, sRedoList; +static BLocker sLock("undo"); + +class UndoItemCopy : public UndoItem { + public: + UndoItemCopy(BObjectList *sourceList, BDirectory &target, + BList *pointList, uint32 moveMode); + virtual ~UndoItemCopy(); + + virtual status_t Undo(); + virtual status_t Redo(); + virtual void UpdateEntry(BEntry *entry, const char *name); + + private: + BObjectList fSourceList; + BObjectList fTargetList; + entry_ref fSourceRef, fTargetRef; + uint32 fMoveMode; +}; + +class UndoItemMove : public UndoItem { + public: + /** source - list of file(s) that were moved. Assumes ownership. + * origfolder - location it was moved from + */ + UndoItemMove(BObjectList *sourceList, BDirectory &target, BList *pointList); + virtual ~UndoItemMove(); + + virtual status_t Undo(); + virtual status_t Redo(); + + private: + BObjectList fSourceList; + entry_ref fSourceRef, fTargetRef; +}; + +class UndoItemFolder : public UndoItem { + public: + UndoItemFolder(const entry_ref &ref); + // ref - entry_ref indicating the folder created + virtual ~UndoItemFolder(); + + virtual status_t Undo(); + virtual status_t Redo(); + + private: + /* this ref has two different meanings in the different states of this object: + - Undo() - fRef indicates the folder that was created via FSCreateNewFolderIn(...) + - Redo() - fRef indicates the folder in which FSCreateNewFolderIn() should be performed + */ + entry_ref fRef; +}; + +class UndoItemRename : public UndoItem { + public: + UndoItemRename(const entry_ref &origRef, const entry_ref &ref); + UndoItemRename(const BEntry &entry, const char *newName); + virtual ~UndoItemRename(); + + virtual status_t Undo(); + virtual status_t Redo(); + + private: + entry_ref fRef, fOrigRef; +}; + +class UndoItemRenameVolume : public UndoItem { + public: + UndoItemRenameVolume(BVolume &volume, const char *newName); + virtual ~UndoItemRenameVolume(); + + virtual status_t Undo(); + virtual status_t Redo(); + + private: + BVolume fVolume; + BString fOldName, fNewName; +}; + + +//-------------------------- + + +static status_t +ChangeListSource(BObjectList &list, BEntry &entry) +{ + node_ref source; + if (entry.GetNodeRef(&source) != B_OK) + return B_ERROR; + + for (int32 index = 0; index < list.CountItems(); index++) { + entry_ref *ref = list.ItemAt(index); + + ref->device = source.device; + ref->directory = source.node; + } + + return B_OK; +} + + +static void +AddUndoItem(UndoItem *item) +{ + BAutolock locker(sLock); + + // we have a restricted number of possible undos + if (sUndoList.CountItems() == kUndoRedoListMaxCount) + sUndoList.RemoveItem(sUndoList.LastItem()); + + sUndoList.AddItem(item, 0); + sRedoList.MakeEmpty(); +} + + +// #pragma mark - + + +Undo::~Undo() +{ + if (fUndo != NULL) + AddUndoItem(fUndo); +} + + +void +Undo::UpdateEntry(BEntry *entry, const char *destName) +{ + if (fUndo != NULL) + fUndo->UpdateEntry(entry, destName); +} + + +void +Undo::Remove() +{ + delete fUndo; + fUndo = NULL; +} + + +MoveCopyUndo::MoveCopyUndo(BObjectList *sourceList, BDirectory &dest, + BList *pointList, uint32 moveMode) +{ + if (moveMode == kMoveSelectionTo) + fUndo = new UndoItemMove(sourceList, dest, pointList); + else + fUndo = new UndoItemCopy(sourceList, dest, pointList, moveMode); +} + + +NewFolderUndo::NewFolderUndo(const entry_ref &ref) +{ + fUndo = new UndoItemFolder(ref); +} + + +RenameUndo::RenameUndo(BEntry &entry, const char *newName) +{ + fUndo = new UndoItemRename(entry, newName); +} + + +RenameVolumeUndo::RenameVolumeUndo(BVolume &volume, const char *newName) +{ + fUndo = new UndoItemRenameVolume(volume, newName); +} + + +// #pragma mark - + + +UndoItemCopy::UndoItemCopy(BObjectList *sourceList, BDirectory &target, + BList */*pointList*/, uint32 moveMode) + : + fSourceList(*sourceList), + fTargetList(*sourceList), + fMoveMode(moveMode) +{ + BEntry entry(sourceList->ItemAt(0)); + + BEntry sourceEntry; + entry.GetParent(&sourceEntry); + sourceEntry.GetRef(&fSourceRef); + + BEntry targetEntry; + target.GetEntry(&targetEntry); + targetEntry.GetRef(&fTargetRef); + ChangeListSource(fTargetList, targetEntry); +} + + +UndoItemCopy::~UndoItemCopy() +{ +} + + +status_t +UndoItemCopy::Undo() +{ + FSDeleteRefList(new BObjectList(fTargetList), true, false); + return B_OK; +} + + +status_t +UndoItemCopy::Redo() +{ + FSMoveToFolder(new BObjectList(fSourceList), new BEntry(&fTargetRef), + FSUndoMoveMode(fMoveMode), NULL); + + return B_OK; +} + + +void +UndoItemCopy::UpdateEntry(BEntry *entry, const char *name) +{ + entry_ref changedRef; + if (entry->GetRef(&changedRef) != B_OK) + return; + + for (int32 index = 0; index < fSourceList.CountItems(); index++) { + entry_ref *ref = fSourceList.ItemAt(index); + if (changedRef != *ref) + continue; + + ref = fTargetList.ItemAt(index); + ref->set_name(name); + } +} + + +// #pragma mark - + + +UndoItemMove::UndoItemMove(BObjectList *sourceList, BDirectory &target, + BList */*pointList*/) + : + fSourceList(*sourceList) +{ + BEntry entry(sourceList->ItemAt(0)); + BEntry source; + entry.GetParent(&source); + source.GetRef(&fSourceRef); + + BEntry targetEntry; + target.GetEntry(&targetEntry); + targetEntry.GetRef(&fTargetRef); +} + + +UndoItemMove::~UndoItemMove() +{ +} + + +status_t +UndoItemMove::Undo() +{ + BObjectList *list = new BObjectList(fSourceList); + BEntry entry(&fTargetRef); + ChangeListSource(*list, entry); + + // FSMoveToFolder() owns its arguments + FSMoveToFolder(list, new BEntry(&fSourceRef), FSUndoMoveMode(kMoveSelectionTo), NULL); + + return B_OK; +} + + +status_t +UndoItemMove::Redo() +{ + // FSMoveToFolder() owns its arguments + FSMoveToFolder(new BObjectList(fSourceList), new BEntry(&fTargetRef), + FSUndoMoveMode(kMoveSelectionTo), NULL); + + return B_OK; +} + + +// #pragma mark - + + +UndoItemFolder::UndoItemFolder(const entry_ref &ref) + : + fRef(ref) +{ +} + + +UndoItemFolder::~UndoItemFolder() +{ +} + + +status_t +UndoItemFolder::Undo() +{ + FSDelete(new entry_ref(fRef), false, false); + return B_OK; +} + + +status_t +UndoItemFolder::Redo() +{ + return FSCreateNewFolder(&fRef); +} + + +// #pragma mark - + + +UndoItemRename::UndoItemRename(const entry_ref &origRef, const entry_ref &ref) + : + fRef(ref), + fOrigRef(origRef) +{ +} + + +UndoItemRename::UndoItemRename(const BEntry &entry, const char *newName) +{ + entry.GetRef(&fOrigRef); + + fRef = fOrigRef; + fRef.set_name(newName); +} + + +UndoItemRename::~UndoItemRename() +{ +} + + +status_t +UndoItemRename::Undo() +{ + BEntry entry(&fRef, false); + return entry.Rename(fOrigRef.name); +} + + +status_t +UndoItemRename::Redo() +{ + BEntry entry(&fOrigRef, false); + return entry.Rename(fRef.name); +} + + +// #pragma mark - + + +UndoItemRenameVolume::UndoItemRenameVolume(BVolume &volume, const char *newName) + : + fVolume(volume), + fNewName(newName) +{ + char *buffer = fOldName.LockBuffer(B_FILE_NAME_LENGTH); + if (buffer != NULL) { + fVolume.GetName(buffer); + fOldName.UnlockBuffer(); + } +} + + +UndoItemRenameVolume::~UndoItemRenameVolume() +{ +} + + +status_t +UndoItemRenameVolume::Undo() +{ + return fVolume.SetName(fOldName.String()); +} + + +status_t +UndoItemRenameVolume::Redo() +{ + return fVolume.SetName(fNewName.String()); +} + + +// #pragma mark - + + +void +FSUndo() +{ + BAutolock locker(sLock); + + UndoItem *undoItem = sUndoList.FirstItem(); + if (undoItem == NULL) + return; + + undoItem->Undo(); + // ToDo: evaluate return code + + sUndoList.RemoveItem(undoItem); + + if (sRedoList.CountItems() == kUndoRedoListMaxCount) + sRedoList.RemoveItem(sRedoList.LastItem()); + + sRedoList.AddItem(undoItem, 0); +} + + +void +FSRedo() +{ + BAutolock locker(sLock); + + UndoItem *undoItem = sRedoList.FirstItem(); + if (undoItem == NULL) + return; + + undoItem->Redo(); + // ToDo: evaluate return code + + sRedoList.RemoveItem(undoItem); + + if (sUndoList.CountItems() == kUndoRedoListMaxCount) + sUndoList.RemoveItem(sUndoList.LastItem()); + + sUndoList.AddItem(undoItem, 0); +} + +} // namespace BPrivate diff --git a/src/kits/tracker/FSUndoRedo.h b/src/kits/tracker/FSUndoRedo.h new file mode 100644 index 0000000000..80c539402e --- /dev/null +++ b/src/kits/tracker/FSUndoRedo.h @@ -0,0 +1,65 @@ +#ifndef _FS_UNDO_REDO_H +#define _FS_UNDO_REDO_H + +#include "ObjectList.h" +#include + +namespace BPrivate { + +class UndoItem; + +class Undo { + public: + ~Undo(); + void UpdateEntry(BEntry *entry, const char *destName); + void Remove(); + + protected: + UndoItem *fUndo; +}; + +class MoveCopyUndo : public Undo { + public: + MoveCopyUndo(BObjectList *sourceList, BDirectory &dest, + BList *pointList, uint32 moveMode); +}; + +class NewFolderUndo : public Undo { + public: + NewFolderUndo(const entry_ref &ref); +}; + +class RenameUndo : public Undo { + public: + RenameUndo(BEntry &entry, const char *newName); +}; + +class RenameVolumeUndo : public Undo { + public: + RenameVolumeUndo(BVolume &volume, const char *newName); +}; + +static +inline bool FSIsUndoMoveMode(uint32 moveMode) +{ + return (moveMode & '\xff\0\0\0') == 'U\0\0\0'; +} + +static +inline uint32 FSUndoMoveMode(uint32 moveMode) +{ + return (moveMode & ~'\xff\0\0\0') | 'U\0\0\0'; +} + +static +inline uint32 FSMoveMode(uint32 moveMode) +{ + return (moveMode & ~'\xff\0\0\0') | 'T\0\0\0'; +} + +extern void FSUndo(); +extern void FSRedo(); + +} // namespace BPrivate + +#endif /* _FS_UNDO_REDO_H */ diff --git a/src/kits/tracker/FSUtils.cpp b/src/kits/tracker/FSUtils.cpp new file mode 100644 index 0000000000..e2571f0580 --- /dev/null +++ b/src/kits/tracker/FSUtils.cpp @@ -0,0 +1,3361 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// Tracker file system calls. + +// Note - APIs/code in FSUtils.h and FSUtils.cpp is slated for a major cleanup +// -- in other words, you will find a lot of ugly cruft in here + +// ToDo: +// Move most of preflight error checks to the Model level and only keep those +// that have to do with size, reading/writing and name collisions. +// Get rid of all the BList based APIs, use BObjectLists. +// Clean up the error handling, push most of the user interaction out of the +// low level FS calls. + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "Attributes.h" +#include "Bitmaps.h" +#include "Commands.h" +#include "FSUndoRedo.h" +#include "FSUtils.h" +#include "InfoWindow.h" +#include "MimeTypes.h" +#include "Model.h" +#include "OverrideAlert.h" +#include "StatusWindow.h" +#include "Thread.h" +#include "Tracker.h" +#include "TrackerSettings.h" +#include "Utilities.h" + + +enum { + kUserCanceled = B_ERRORS_END + 1, + kCopyCanceled = kUserCanceled, + kTrashCanceled +}; + +enum ConflictCheckResult { + kCanceled = kUserCanceled, + kPrompt, + kReplace, + kReplaceAll, + kNoConflicts +}; + +namespace BPrivate { + +static status_t FSDeleteFolder(BEntry *, CopyLoopControl *, bool updateStatus, + bool deleteTopDir = true, bool upateFileNameInStatus = false); +static status_t MoveEntryToTrash(BEntry *, BPoint *, Undo &undo); +static void LowLevelCopy(BEntry *, StatStruct *, BDirectory *, char *destName, + CopyLoopControl *, BPoint *); +status_t DuplicateTask(BObjectList *srcList); +static status_t MoveTask(BObjectList *, BEntry *, BList *, uint32); +static status_t _DeleteTask(BObjectList *, bool); +static status_t _RestoreTask(BObjectList *); +status_t CalcItemsAndSize(BObjectList *refList, int32 *totalCount, off_t *totalSize); +status_t MoveItem(BEntry *entry, BDirectory *destDir, BPoint *loc, + uint32 moveMode, const char *newName, Undo &undo); +ConflictCheckResult PreFlightNameCheck(BObjectList *srcList, const BDirectory *destDir, + int32 *collisionCount); +status_t CheckName(uint32 moveMode, const BEntry *srcEntry, const BDirectory *destDir, + bool multipleCollisions, ConflictCheckResult &); +void CopyAttributes(CopyLoopControl *control, BNode *srcNode, BNode* destNode, void *buffer, + size_t bufsize); +void CopyPoseLocation(BNode *src, BNode *dest); +bool DirectoryMatchesOrContains(const BEntry *, directory_which); +bool DirectoryMatchesOrContains(const BEntry *, const char *additionalPath, directory_which); +bool DirectoryMatches(const BEntry *, directory_which); +bool DirectoryMatches(const BEntry *, const char *additionalPath, directory_which); + +status_t empty_trash(void *); + + +const char *kDeleteConfirmationStr = "Are you sure you want to delete the selected " + "item(s)? This operation cannot be reverted."; + +const char *kReplaceStr = "You are trying to replace the item:\n" + "\t%s%s\n" + "with:\n" + "\t%s%s\n\n" + "Would you like to replace it with the one you are %s?"; + +const char *kDirectoryReplaceStr = "An item named \"%s\" already exists in this folder. " + "Would you like to replace it with the one you are %s?"; + +const char *kSymLinkReplaceStr = "An item named \"%s\" already exists in this folder. " + "Would you like to replace it with the symbolic link you are creating?"; + +const char *kNoFreeSpace = "Sorry, there is not enough free space on the destination " + "volume to copy the selection."; + +const char *kFileErrorString = "Error copying file \"%s\":\n\t%s\n\nWould you like to continue?"; +const char *kFolderErrorString = "Error copying folder \"%s\":\n\t%s\n\nWould you like to continue?"; +const char *kFileDeleteErrorString = "There was an error deleting \"%s\":\n\t%s"; +const char *kReplaceManyStr = "Some items already exist in this folder with " + "the same names as the items you are %s.\n \nWould you like to replace them " + "with the ones you are %s or be prompted for each one?"; + +const char *kFindAlternativeStr = "Would you like to find some other suitable application?"; +const char *kFindApplicationStr = "Would you like to find a suitable application to " + "open the file?"; + +// Skip these attributes when copying in Tracker +const char *kSkipAttributes[] = { + kAttrPoseInfo, + NULL +}; + + +CopyLoopControl::~CopyLoopControl() +{ +} + + +bool +TrackerCopyLoopControl::FileError(const char *message, const char *name, + status_t error, bool allowContinue) +{ + char buffer[512]; + sprintf(buffer, message, name, strerror(error)); + + if (allowContinue) + return (new BAlert("", buffer, "Cancel", "OK", 0, + B_WIDTH_AS_USUAL, B_STOP_ALERT))->Go() != 0; + + (new BAlert("", buffer, "Cancel", 0, 0, + B_WIDTH_AS_USUAL, B_STOP_ALERT))->Go(); + return false; +} + + +void +TrackerCopyLoopControl::UpdateStatus(const char *name, entry_ref, int32 count, + bool optional) +{ + if (gStatusWindow && gStatusWindow->HasStatus(fThread)) + gStatusWindow->UpdateStatus(fThread, const_cast(name), + count, optional); +} + + +bool +TrackerCopyLoopControl::CheckUserCanceled() +{ + return gStatusWindow && gStatusWindow->CheckCanceledOrPaused(fThread); +} + + +TrackerCopyLoopControl::OverwriteMode +TrackerCopyLoopControl::OverwriteOnConflict(const BEntry *, const char *, + const BDirectory *, bool, bool) +{ + return kReplace; +} + + +bool +TrackerCopyLoopControl::SkipEntry(const BEntry *, bool) +{ + // tracker makes no exceptions + return false; +} + + +bool +TrackerCopyLoopControl::SkipAttribute(const char *attributeName) +{ + for (const char **skipAttribute = kSkipAttributes; *skipAttribute; + skipAttribute++) + if (strcmp(*skipAttribute, attributeName) == 0) + return true; + + return false; +} + + +void +CopyLoopControl::ChecksumChunk(const char *, size_t) +{ +} + + +bool +CopyLoopControl::ChecksumFile(const entry_ref *) +{ + return true; +} + + +bool +CopyLoopControl::SkipAttribute(const char*) +{ + return false; +} + + +bool +CopyLoopControl::PreserveAttribute(const char*) +{ + return false; +} + + +static BNode * +GetWritableNode(BEntry *entry, StatStruct *statBuf = 0) +{ + // utility call that works around the problem with BNodes not being + // universally writeable + // BNodes created on files will fail to WriteAttr because they do not + // have the right r/w permissions + + StatStruct localStatbuf; + + if (!statBuf) { + statBuf = &localStatbuf; + if (entry->GetStat(statBuf) != B_OK) + return 0; + } + + if (S_ISREG(statBuf->st_mode)) + return new BFile(entry, O_RDWR); + + return new BNode(entry); +} + + +status_t +FSSetPoseLocation(ino_t destDirInode, BNode *destNode, BPoint point) +{ + PoseInfo poseInfo; + poseInfo.fInvisible = false; + poseInfo.fInitedDirectory = destDirInode; + poseInfo.fLocation = point; + + status_t result = destNode->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, + &poseInfo, sizeof(poseInfo)); + + if (result == sizeof(poseInfo)) + return B_OK; + + return result; +} + + +status_t +FSSetPoseLocation(BEntry *entry, BPoint point) +{ + BNode node(entry); + status_t result = node.InitCheck(); + if (result != B_OK) + return result; + + BDirectory parent; + result = entry->GetParent(&parent); + if (result != B_OK) + return result; + + node_ref destNodeRef; + result = parent.GetNodeRef(&destNodeRef); + if (result != B_OK) + return result; + + return FSSetPoseLocation(destNodeRef.node, &node, point); +} + + +bool +FSGetPoseLocation(const BNode *node, BPoint *point) +{ + PoseInfo poseInfo; + if (ReadAttr(node, kAttrPoseInfo, kAttrPoseInfoForeign, + B_RAW_TYPE, 0, &poseInfo, sizeof(poseInfo), &PoseInfo::EndianSwap) + == kReadAttrFailed) + return false; + + if (poseInfo.fInitedDirectory == -1LL) + return false; + + *point = poseInfo.fLocation; + + return true; +} + + +static void +SetUpPoseLocation(ino_t sourceParentIno, ino_t destParentIno, + const BNode *sourceNode, BNode *destNode, BPoint *loc) +{ + BPoint point; + if (!loc + // we don't have a position yet + && sourceParentIno != destParentIno + // we aren't copying into the same directory + && FSGetPoseLocation(sourceNode, &point)) + // the original has a valid inited location + loc = &point; + // copy the originals location + + if (loc && loc != (BPoint *)-1) { + // loc of -1 is used when copying/moving into a window in list mode + // where copying positions would not work + // ToSo: + // should push all this logic to upper levels + FSSetPoseLocation(destParentIno, destNode, *loc); + } +} + + +void +FSMoveToFolder(BObjectList *srcList, BEntry *destEntry, + uint32 moveMode, BList *pointList) +{ + if (srcList->IsEmpty()) { + delete srcList; + delete pointList; + delete destEntry; + return; + } + + LaunchInNewThread("MoveTask", B_NORMAL_PRIORITY, MoveTask, srcList, destEntry, + pointList, moveMode); +} + + +void +FSDelete(entry_ref *ref, bool async, bool confirm) +{ + BObjectList *list = new BObjectList(1, true); + list->AddItem(ref); + FSDeleteRefList(list, async, confirm); +} + + +void +FSDeleteRefList(BObjectList *list, bool async, bool confirm) +{ + if (async) + LaunchInNewThread("DeleteTask", B_NORMAL_PRIORITY, _DeleteTask, list, confirm); + else + _DeleteTask(list, confirm); +} + + +void +FSRestoreRefList(BObjectList *list, bool async) +{ + if (async) + LaunchInNewThread("RestoreTask", B_NORMAL_PRIORITY, _RestoreTask, list); + else + _RestoreTask(list); +} + + +void +FSMoveToTrash(BObjectList *srcList, BList *pointList, bool async) +{ + if (srcList->IsEmpty()) { + delete srcList; + delete pointList; + return; + } + + if (async) + LaunchInNewThread("MoveTask", B_NORMAL_PRIORITY, MoveTask, srcList, + (BEntry *)0, pointList, kMoveSelectionTo); + else + MoveTask(srcList, 0, pointList, kMoveSelectionTo); +} + + +static bool +IsDisksWindowIcon(BEntry *entry) +{ + BPath path; + if (entry->InitCheck() != B_OK || entry->GetPath(&path) != B_OK) + return false; + + return strcmp(path.Path(), "/") == 0; +} + +enum { + kNotConfirmed, + kConfirmedHomeMove, + kConfirmedAll +}; + + +bool +ConfirmChangeIfWellKnownDirectory(const BEntry *entry, const char *action, + bool dontAsk, int32 *confirmedAlready) +{ + // Don't let the user casually move/change important files/folders + // + // This is a cheap replacement for having a real UID support turned + // on and not running as root all the time + + if (confirmedAlready && *confirmedAlready == kConfirmedAll) + return true; + + if (!DirectoryMatchesOrContains(entry, B_BEOS_DIRECTORY) + && !DirectoryMatchesOrContains(entry, B_USER_DIRECTORY)) + // quick way out + return true; + + const char *warning = NULL; + bool requireOverride = true; + + if (DirectoryMatches(entry, B_BEOS_DIRECTORY)) + warning = "If you %s the beos folder, you won't be able to " + "boot BeOS! Are you sure you want to do this? To %s the folder " + "anyway, hold down the Shift key and click \"Do it\"."; + else if (DirectoryMatchesOrContains(entry, B_BEOS_SYSTEM_DIRECTORY)) + warning = "If you %s the system folder or its contents, you " + "won't be able to boot BeOS! Are you sure you want to do this? " + "To %s the system folder or its contents anyway, hold down " + "the Shift key and click \"Do it\"."; + else if (DirectoryMatches(entry, B_USER_DIRECTORY)) { + warning = "If you %s the home folder, BeOS may not " + "behave properly! Are you sure you want to do this? " + "To %s the home anyway, click \"Do it\"."; + requireOverride = false; + } else if (DirectoryMatchesOrContains(entry, B_USER_CONFIG_DIRECTORY) + || DirectoryMatchesOrContains(entry, B_COMMON_SETTINGS_DIRECTORY)) { + + if (DirectoryMatchesOrContains(entry, "beos_mime", B_USER_SETTINGS_DIRECTORY) + || DirectoryMatchesOrContains(entry, "beos_mime", B_COMMON_SETTINGS_DIRECTORY)) { + warning = "If you %s the mime settings, BeOS may not " + "behave properly! Are you sure you want to do this? " + "To %s the mime settings anyway, click \"Do it\"."; + requireOverride = false; + } else if (DirectoryMatches(entry, B_USER_CONFIG_DIRECTORY)) { + warning = "If you %s the config folder, BeOS may not " + "behave properly! Are you sure you want to do this? " + "To %s the config folder anyway, click \"Do it\"."; + requireOverride = false; + } else if (DirectoryMatches(entry, B_USER_SETTINGS_DIRECTORY) + || DirectoryMatches(entry, B_COMMON_SETTINGS_DIRECTORY)) { + warning = "If you %s the settings folder, BeOS may not " + "behave properly! Are you sure you want to do this? " + "To %s the settings folder anyway, click \"Do it\"."; + requireOverride = false; + } + } + + if (!warning) + return true; + + if (dontAsk) + return false; + + if (confirmedAlready && *confirmedAlready == kConfirmedHomeMove + && !requireOverride) + // we already warned about moving home this time around + return true; + + char buffer[256]; + sprintf(buffer, warning, action, action); + + if ((new OverrideAlert("", buffer, "Do it", (requireOverride ? B_SHIFT_KEY : 0), + "Cancel", 0, NULL, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go() == 1) { + if (confirmedAlready) + *confirmedAlready = kNotConfirmed; + return false; + } + + if (confirmedAlready) { + if (!requireOverride) + *confirmedAlready = kConfirmedHomeMove; + else + *confirmedAlready = kConfirmedAll; + } + + return true; +} + + +static status_t +InitCopy(uint32 moveMode, BObjectList *srcList, thread_id thread, + BVolume *dstVol, BDirectory *destDir, entry_ref *destRef, + bool preflightNameCheck, int32 *collisionCount, ConflictCheckResult *preflightResult) +{ + if (dstVol->IsReadOnly()) { + if (gStatusWindow) + gStatusWindow->RemoveStatusItem(thread); + + (new BAlert("", "You can't move or copy items to read-only volumes.", + "Cancel", 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); + return B_ERROR; + } + + int32 numItems = srcList->CountItems(); + int32 askOnceOnly = kNotConfirmed; + for (int32 index = 0; index < numItems; index++) { + // we could check for this while iterating through items in each of the copy + // loops, except it takes forever to call CalcItemsAndSize + BEntry entry((entry_ref *)srcList->ItemAt(index)); + if (IsDisksWindowIcon(&entry)) { + + const char *errorStr; + if (moveMode == kCreateLink) + errorStr = "You cannot create a link to the root directory."; + else + errorStr = "You cannot copy or move the root directory."; + + (new BAlert("", errorStr, "Cancel", 0, 0, + B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); + return B_ERROR; + } + if (moveMode == kMoveSelectionTo + && !ConfirmChangeIfWellKnownDirectory(&entry, "move", false, &askOnceOnly)) + return B_ERROR; + } + + if (preflightNameCheck) { + ASSERT(collisionCount); + ASSERT(preflightResult); + + *preflightResult = kPrompt; + *collisionCount = 0; + + *preflightResult = PreFlightNameCheck(srcList, destDir, collisionCount); + if (*preflightResult == kCanceled) // user canceled + return B_ERROR; + } + + // set up the status display + switch (moveMode) { + case kCopySelectionTo: + case kDuplicateSelection: + { + if (gStatusWindow) + gStatusWindow->CreateStatusItem(thread, kCopyState); + + int32 totalItems = 0; + off_t totalSize = 0; + if (CalcItemsAndSize(srcList, &totalItems, &totalSize) != B_OK) + return B_ERROR; + + // check for free space before starting copy + if ((totalSize + (4 * kKBSize)) >= dstVol->FreeBytes()) { + (new BAlert("", kNoFreeSpace, "Cancel", 0, 0, + B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); + return B_ERROR; + } + + if (gStatusWindow) + gStatusWindow->InitStatusItem(thread, totalItems, totalSize, + destRef); + break; + } + + case kMoveSelectionTo: + case kCreateLink: + if (numItems > 10) { + // this will be fast, only put up status if lots of items + // moved, links created + if (gStatusWindow) { + gStatusWindow->CreateStatusItem(thread, + moveMode == kMoveSelectionTo + ? kMoveState : kCreateLinkState); + gStatusWindow->InitStatusItem(thread, numItems, numItems, + destRef); + } + } + break; + } + return B_OK; +} + + +// ToDo: +// get rid of this cruft +bool +delete_ref(void *ref) +{ + delete (entry_ref*)ref; + return false; +} + + +bool +delete_point(void *point) +{ + delete (BPoint*)point; + return false; +} + + +static status_t +MoveTask(BObjectList *srcList, BEntry *destEntry, BList *pointList, uint32 moveMode) +{ + ASSERT(!srcList->IsEmpty()); + + // extract information from src, dest models + // ## note that we're assuming all items come from the same volume + // ## by looking only at FirstItem here which is not a good idea + dev_t srcVolumeDevice = srcList->FirstItem()->device; + dev_t destVolumeDevice = srcVolumeDevice; + + StatStruct deststat; + BVolume volume; + entry_ref destRef; + const entry_ref *destRefToCheck = NULL; + + bool destIsTrash = false; + BDirectory destDir; + BDirectory *destDirToCheck = NULL; + bool needPreflightNameCheck = false; + + bool fromUndo = FSIsUndoMoveMode(moveMode); + moveMode = FSMoveMode(moveMode); + + // if we're not passed a destEntry then we are supposed to move to trash + if (destEntry) { + destEntry->GetRef(&destRef); + destRefToCheck = &destRef; + + destDir.SetTo(destEntry); + destDir.GetStat(&deststat); + destDirToCheck = &destDir; + + destVolumeDevice = deststat.st_dev; + destIsTrash = FSIsTrashDir(destEntry); + volume.SetTo(destVolumeDevice); + + needPreflightNameCheck = true; + } else if (moveMode == kDuplicateSelection) + volume.SetTo(srcVolumeDevice); + else { + // move is to trash + destIsTrash = true; + + FSGetTrashDir(&destDir, srcVolumeDevice); + volume.SetTo(srcVolumeDevice); + + BEntry entry; + destDir.GetEntry(&entry); + destDirToCheck = &destDir; + + entry.GetRef(&destRef); + destRefToCheck = &destRef; + } + + // change the move mode if needed + if (moveMode == kMoveSelectionTo && srcVolumeDevice != destVolumeDevice) + // move across volumes - copy instead + moveMode = kCopySelectionTo; + if (moveMode == kCopySelectionTo && destIsTrash) + // cannot copy to trash + moveMode = kMoveSelectionTo; + + // we need the undo object later on, so we create it no matter + // if we really need it or not (it's very lightweight) + MoveCopyUndo undo(srcList, destDir, pointList, moveMode); + if (fromUndo) + undo.Remove(); + + thread_id thread = find_thread(NULL); + ConflictCheckResult conflictCheckResult = kPrompt; + int32 collisionCount = 0; + status_t result = InitCopy(moveMode, srcList, thread, &volume, destDirToCheck, + &destRef, needPreflightNameCheck, &collisionCount, &conflictCheckResult); + + int32 count = srcList->CountItems(); + if (result == B_OK) { + for (int32 i = 0; i < count; i++) { + BPoint *loc = (BPoint *)-1; + // a loc of -1 forces autoplacement, rather than copying the + // position of the original node + // ToDo: + // clean this mess up + + entry_ref *srcRef = srcList->ItemAt(i); + + if (moveMode == kDuplicateSelection) { + BEntry entry(srcRef); + entry.GetParent(&destDir); + destDir.GetStat(&deststat); + volume.SetTo(srcRef->device); + } + + // handle case where item is dropped into folder it already lives in + // which could happen if dragging from a query window + if (moveMode != kCreateLink + && moveMode != kCreateRelativeLink + && moveMode != kDuplicateSelection + && !destIsTrash + && (srcRef->device == destRef.device + && srcRef->directory == deststat.st_ino)) + continue; + + if (gStatusWindow && gStatusWindow->CheckCanceledOrPaused(thread)) + break; + + BEntry sourceEntry(srcRef); + if (sourceEntry.InitCheck() != B_OK) { + BString error; + error << "Error moving \"" << srcRef->name << "\"."; + (new BAlert("", error.String(), "Cancel", 0, 0, + B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); + break; + } + + // are we moving item to trash? + if (destIsTrash) { + if (pointList) + loc = (BPoint *)pointList->ItemAt(i); + + result = MoveEntryToTrash(&sourceEntry, loc, undo); + if (result != B_OK) { + BString error; + error << "Error moving \"" << srcRef->name << "\" to Trash. (" + << strerror(result) << ")"; + (new BAlert("", error.String(), "Cancel", 0, 0, + B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); + break; + } + continue; + } + + // resolve name collisions and hierarchy problems + if (CheckName(moveMode, &sourceEntry, &destDir, collisionCount > 1, + conflictCheckResult) != B_OK) { + // we will skip the current item, because we got a conflict + // and were asked to or because there was some conflict + + // update the status because item got skipped and the status + // will not get updated by the move call + if (gStatusWindow && gStatusWindow->HasStatus(thread)) + gStatusWindow->UpdateStatus(thread, srcRef->name, 1); + + continue; + } + + // get location to place this item + if (pointList && moveMode != kCopySelectionTo) { + loc = (BPoint *)pointList->ItemAt(i); + + BNode *src_node = GetWritableNode(&sourceEntry); + if (src_node && src_node->InitCheck() == B_OK) { + PoseInfo poseInfo; + poseInfo.fInvisible = false; + poseInfo.fInitedDirectory = deststat.st_ino; + poseInfo.fLocation = *loc; + src_node->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, + &poseInfo, sizeof(poseInfo)); + } + delete src_node; + } + + if (pointList) + loc = (BPoint*)pointList->ItemAt(i); + + result = MoveItem(&sourceEntry, &destDir, loc, moveMode, NULL, undo); + if (result != B_OK) + break; + } + } + + // duplicates of srcList, destFolder were created - dispose them + delete srcList; + delete destEntry; + + // delete file location list and all Points within + if (pointList) { + pointList->DoForEach(delete_point); + delete pointList; + } + + if (gStatusWindow) + gStatusWindow->RemoveStatusItem(thread); + + return B_OK; +} + +class FailWithAlert { + public: + static void FailOnError(status_t error, const char *string, const char *name = NULL) + { + if (error != B_OK) + throw FailWithAlert(error, string, name); + } + + FailWithAlert(status_t error, const char *string, const char *name) + : fString(string), + fName(name), + fError(error) + { + } + + const char *fString; + const char *fName; + status_t fError; +}; + +class MoveError { + public: + static void FailOnError(status_t error) + { + if (error != B_OK) + throw MoveError(error); + } + + MoveError(status_t error) + : fError(error) + { } + + status_t fError; +}; + + +void +CopyFile(BEntry *srcFile, StatStruct *srcStat, BDirectory *destDir, + CopyLoopControl *loopControl, BPoint *loc, bool makeOriginalName, Undo &undo) +{ + if (loopControl->SkipEntry(srcFile, true)) + return; + + node_ref node; + destDir->GetNodeRef(&node); + BVolume volume(node.device); + + // check for free space first + if ((srcStat->st_size + kKBSize) >= volume.FreeBytes()) { + loopControl->FileError(kNoFreeSpace, "", B_DEVICE_FULL, false); + throw (status_t)B_DEVICE_FULL; + } + + char destName[B_FILE_NAME_LENGTH]; + srcFile->GetName(destName); + entry_ref ref; + srcFile->GetRef(&ref); + + loopControl->UpdateStatus(destName, ref, 1024, true); + + if (makeOriginalName) { + FSMakeOriginalName(destName, destDir, " copy"); + undo.UpdateEntry(srcFile, destName); + } + + BEntry conflictingEntry; + if (destDir->FindEntry(destName, &conflictingEntry) == B_OK) { + switch (loopControl->OverwriteOnConflict(srcFile, destName, destDir, + false, false)) { + case TrackerCopyLoopControl::kSkip: + // we are about to ignore this entire directory + return; + + case TrackerCopyLoopControl::kReplace: + if (conflictingEntry.IsDirectory()) + // remove existing folder recursively + ThrowOnError(FSDeleteFolder(&conflictingEntry, loopControl, false)); + else + ThrowOnError(conflictingEntry.Remove()); + break; + + case TrackerCopyLoopControl::kMerge: + // This flag implies that the attributes should be kept + // on the file. Just ignore it. + break; + } + } + + try { + LowLevelCopy(srcFile, srcStat, destDir, destName, loopControl, loc); + } catch (status_t err) { + if (err == kCopyCanceled) + throw (status_t)err; + + if (err != B_OK) { + if (!loopControl->FileError(kFileErrorString, destName, err, true)) + throw (status_t)err; + else + // user selected continue in spite of error, update status bar + loopControl->UpdateStatus(NULL, ref, (int32)srcStat->st_size); + } + } +} + + +#ifdef _SILENTLY_CORRECT_FILE_NAMES +static bool +CreateFileSystemCompatibleName(const BDirectory *destDir, char *destName) +{ + // Is it a FAT32 file system? (this is the only one we currently now about) + + BEntry target; + destDir->GetEntry(&target); + entry_ref targetRef; + fs_info info; + if (target.GetRef(&targetRef) == B_OK + && fs_stat_dev(targetRef.device, &info) == B_OK + && !strcmp(info.fsh_name, "dos")) { + bool wasInvalid = false; + + // it's a FAT32 file system, now check the name + + int32 length = strlen(destName) - 1; + while (destName[length] == '.') { + // invalid name, just cut off the dot at the end + destName[length--] = '\0'; + wasInvalid = true; + } + + char *invalid = destName; + while ((invalid = strpbrk(invalid, "?<>\\:\"|*")) != NULL) { + invalid[0] = '_'; + wasInvalid = true; + } + + return wasInvalid; + } + + return false; +} +#endif + + +static void +LowLevelCopy(BEntry *srcEntry, StatStruct *srcStat, BDirectory *destDir, + char *destName, CopyLoopControl *loopControl, BPoint *loc) +{ + entry_ref ref; + ThrowOnError(srcEntry->GetRef(&ref)); + + if (S_ISLNK(srcStat->st_mode)) { + // handle symbolic links + BSymLink srcLink; + BSymLink newLink; + char linkpath[MAXPATHLEN]; + + ThrowOnError(srcLink.SetTo(srcEntry)); + ThrowIfNotSize(srcLink.ReadLink(linkpath, MAXPATHLEN-1)); + + ThrowOnError(destDir->CreateSymLink(destName, linkpath, &newLink)); + + node_ref destNodeRef; + destDir->GetNodeRef(&destNodeRef); + // copy or write new pose location as a first thing + SetUpPoseLocation(ref.directory, destNodeRef.node, &srcLink, + &newLink, loc); + + BNodeInfo nodeInfo(&newLink); + ThrowOnError(nodeInfo.SetType(B_LINK_MIMETYPE)); + + newLink.SetPermissions(srcStat->st_mode); + newLink.SetOwner(srcStat->st_uid); + newLink.SetGroup(srcStat->st_gid); + newLink.SetModificationTime(srcStat->st_mtime); + newLink.SetCreationTime(srcStat->st_crtime); + + return; + } + + BFile srcFile(srcEntry, O_RDONLY); + ThrowOnInitCheckError(&srcFile); + + const size_t kMinBufferSize = 1024 * 128; + const size_t kMaxBufferSize = 1024 * 1024; + + size_t bufsize = kMinBufferSize; + if (bufsize < srcStat->st_size) { + // File bigger than the buffer size: determine an optimal buffer size + system_info sinfo; + get_system_info(&sinfo); + size_t freesize = static_cast((sinfo.max_pages - sinfo.used_pages) * B_PAGE_SIZE); + bufsize = freesize / 4; // take 1/4 of RAM max + bufsize -= bufsize % (16 * 1024); // Round to 16 KB boundaries + if (bufsize < kMinBufferSize) // at least kMinBufferSize + bufsize = kMinBufferSize; + else if (bufsize > kMaxBufferSize) // no more than kMaxBufferSize + bufsize = kMaxBufferSize; + } + + BFile destFile(destDir, destName, O_RDWR | O_CREAT); +#ifdef _SILENTLY_CORRECT_FILE_NAMES + if ((destFile.InitCheck() == B_BAD_VALUE || destFile.InitCheck() == B_NOT_ALLOWED) + && CreateFileSystemCompatibleName(destDir, destName)) + destFile.SetTo(destDir, destName, B_CREATE_FILE | B_READ_WRITE); +#endif + + ThrowOnInitCheckError(&destFile); + + node_ref destNodeRef; + destDir->GetNodeRef(&destNodeRef); + // copy or write new pose location as a first thing + SetUpPoseLocation(ref.directory, destNodeRef.node, &srcFile, + &destFile, loc); + + char *buffer = new char[bufsize]; + try { + // copy data portion of file + while (true) { + if (loopControl->CheckUserCanceled()) { + // if copy was canceled, remove partial destination file + destFile.Unset(); + + BEntry destEntry; + if (destDir->FindEntry(destName, &destEntry) == B_OK) + destEntry.Remove(); + + throw (status_t)kCopyCanceled; + } + + ASSERT(buffer); + ssize_t bytes = srcFile.Read(buffer, bufsize); + + if (bytes > 0) { + ssize_t updateBytes = 0; + if (bytes > 32 * 1024) { + // when copying large chunks, update after read and after write + // to get better update granularity + updateBytes = bytes / 2; + loopControl->UpdateStatus(NULL, ref, updateBytes, true); + } + + loopControl->ChecksumChunk(buffer, (size_t)bytes); + + ssize_t result = destFile.Write(buffer, (size_t)bytes); + if (result != bytes) + throw (status_t)B_ERROR; + + loopControl->UpdateStatus(NULL, ref, bytes - updateBytes, true); + } else if (bytes < 0) + // read error + throw (status_t)bytes; + else + // we are done + break; + } + + CopyAttributes(loopControl, &srcFile, &destFile, buffer, bufsize); + } catch (...) { + delete [] buffer; + throw; + } + + destFile.SetPermissions(srcStat->st_mode); + destFile.SetOwner(srcStat->st_uid); + destFile.SetGroup(srcStat->st_gid); + destFile.SetModificationTime(srcStat->st_mtime); + destFile.SetCreationTime(srcStat->st_crtime); + + delete [] buffer; + + if (!loopControl->ChecksumFile(&ref)) { + // File no good. Remove and quit. + destFile.Unset(); + + BEntry destEntry; + if (destDir->FindEntry(destName, &destEntry) == B_OK) + destEntry.Remove(); + throw (status_t)kUserCanceled; + } +} + + +void +CopyAttributes(CopyLoopControl *control, BNode *srcNode, BNode *destNode, void *buffer, + size_t bufsize) +{ + // ToDo: + // Add error checking + // prior to coyping attributes, make sure indices are installed + + // When calling CopyAttributes on files, have to make sure destNode + // is a BFile opened R/W + + srcNode->RewindAttrs(); + char name[256]; + while (srcNode->GetNextAttrName(name) == B_OK) { + // Check to see if this attribute should be skipped. + if (control->SkipAttribute(name)) + continue; + + attr_info info; + if (srcNode->GetAttrInfo(name, &info) != B_OK) + continue; + + // Check to see if this attribute should be overwritten when it + // already exists. + if (control->PreserveAttribute(name)) { + attr_info dest_info; + if (destNode->GetAttrInfo(name, &dest_info) == B_OK) + continue; + } + + ssize_t bytes; + ssize_t numToRead = (ssize_t)info.size; + for (off_t offset = 0; numToRead > 0; offset += bytes) { + size_t chunkSize = (size_t)numToRead; + if (chunkSize > bufsize) + chunkSize = bufsize; + + bytes = srcNode->ReadAttr(name, info.type, offset, + buffer, chunkSize); + + if (bytes <= 0) + break; + + destNode->WriteAttr(name, info.type, offset, buffer, (size_t)bytes); + + numToRead -= bytes; + } + } +} + + +static void +CopyFolder(BEntry *srcEntry, BDirectory *destDir, CopyLoopControl *loopControl, + BPoint *loc, bool makeOriginalName, Undo &undo) +{ + BDirectory newDir; + BEntry entry; + status_t err = B_OK; + bool createDirectory = true; + BEntry existingEntry; + + if (loopControl->SkipEntry(srcEntry, false)) + return; + + entry_ref ref; + srcEntry->GetRef(&ref); + + char destName[B_FILE_NAME_LENGTH]; + strcpy(destName, ref.name); + + loopControl->UpdateStatus(ref.name, ref, 1024, true); + + if (makeOriginalName) { + FSMakeOriginalName(destName, destDir, " copy"); + undo.UpdateEntry(srcEntry, destName); + } + + if (destDir->FindEntry(destName, &existingEntry) == B_OK) { + // some entry with a conflicting name is already present in destDir + // decide what to do about it + bool isDirectory = existingEntry.IsDirectory(); + + switch (loopControl->OverwriteOnConflict(srcEntry, destName, destDir, + true, isDirectory)) { + case TrackerCopyLoopControl::kSkip: + // we are about to ignore this entire directory + return; + + case TrackerCopyLoopControl::kReplace: + if (isDirectory) + // remove existing folder recursively + ThrowOnError(FSDeleteFolder(&existingEntry, loopControl, false)); + + else + // conflicting with a file or symbolic link, remove entry + ThrowOnError(existingEntry.Remove()); + break; + + case TrackerCopyLoopControl::kMerge: + ASSERT(isDirectory); + // do not create a new directory, use the current one + newDir.SetTo(&existingEntry); + createDirectory = false; + break; + } + } + + // loop through everything in src folder and copy it to new folder + BDirectory srcDir(srcEntry); + srcDir.Rewind(); + srcEntry->Unset(); + + // create a new folder inside of destination folder + if (createDirectory) { + err = destDir->CreateDirectory(destName, &newDir); +#ifdef _SILENTLY_CORRECT_FILE_NAMES + if (err == B_BAD_VALUE) { + // check if it's an invalid name on a FAT32 file system + if (CreateFileSystemCompatibleName(destDir, destName)) + err = destDir->CreateDirectory(destName, &newDir); + } +#endif + if (err != B_OK) { + if (!loopControl->FileError(kFolderErrorString, destName, err, true)) + throw err; + + // will allow rest of copy to continue + return; + } + } + + char *buffer; + if (createDirectory && err == B_OK && (buffer = (char*)malloc(32768)) != 0) { + CopyAttributes(loopControl, &srcDir, &newDir, buffer, 32768); + // don't copy original pose location if new location passed + free(buffer); + } + + StatStruct statbuf; + srcDir.GetStat(&statbuf); + dev_t sourceDeviceID = statbuf.st_dev; + + // copy or write new pose location + node_ref destNodeRef; + destDir->GetNodeRef(&destNodeRef); + SetUpPoseLocation(ref.directory, destNodeRef.node, &srcDir, + &newDir, loc); + + while (srcDir.GetNextEntry(&entry) == B_OK) { + + if (loopControl->CheckUserCanceled()) + throw (status_t)kUserCanceled; + + entry.GetStat(&statbuf); + + if (S_ISDIR(statbuf.st_mode)) { + + // entry is a mount point, do not copy it + if (statbuf.st_dev != sourceDeviceID) { + PRINT(("Avoiding mount point %d, %d \n", statbuf.st_dev, sourceDeviceID)); + continue; + } + + CopyFolder(&entry, &newDir, loopControl, 0, false, undo); + } else + CopyFile(&entry, &statbuf, &newDir, loopControl, 0, false, undo); + } +} + + +status_t +MoveItem(BEntry *entry, BDirectory *destDir, BPoint *loc, uint32 moveMode, + const char *newName, Undo &undo) +{ + entry_ref ref; + try { + node_ref destNode; + StatStruct statbuf; + + MoveError::FailOnError(entry->GetStat(&statbuf)); + MoveError::FailOnError(entry->GetRef(&ref)); + MoveError::FailOnError(destDir->GetNodeRef(&destNode)); + + if (moveMode == kCreateLink || moveMode == kCreateRelativeLink) { + PoseInfo poseInfo; + char name[B_FILE_NAME_LENGTH]; + strcpy(name, ref.name); + + BSymLink link; + FSMakeOriginalName(name, destDir, " link"); + undo.UpdateEntry(entry, name); + + BPath path; + entry->GetPath(&path); + if (loc && loc != (BPoint *)-1) { + poseInfo.fInvisible = false; + poseInfo.fInitedDirectory = destNode.node; + poseInfo.fLocation = *loc; + } + + status_t err = B_ERROR; + + if (moveMode == kCreateRelativeLink) { + if (statbuf.st_dev == destNode.device) { + // relative link only works on the same device + char oldwd[B_PATH_NAME_LENGTH]; + getcwd(oldwd, B_PATH_NAME_LENGTH); + + BEntry destEntry; + destDir -> GetEntry(&destEntry); + BPath destPath; + destEntry.GetPath(&destPath); + + chdir(destPath.Path()); + // change working dir to target dir + + BString destString(destPath.Path()); + destString.Append("/"); + + BString srcString(path.Path()); + srcString.RemoveLast(path.Leaf()); + + // find index while paths are the same + + const char *src = srcString.String(); + const char *dest = destString.String(); + const char *lastFolderSrc = src; + const char *lastFolderDest = dest; + + while (*src && *dest && *src == *dest) { + ++src; + if (*dest++ == '/') { + lastFolderSrc = src; + lastFolderDest = dest; + } + } + src = lastFolderSrc; + dest = lastFolderDest; + + BString source; + if (*dest == '\0' && *src != '\0') { + // source is deeper in the same tree than the target + source.Append(src); + } else if (*dest != '\0') { + // target is deeper in the same tree than the source + while (*dest) { + if (*dest == '/') + source.Prepend("../"); + ++dest; + } + source.Append(src); + } + + // else source and target are in the same dir + + source.Append(path.Leaf()); + err = destDir->CreateSymLink(name, source.String(), &link); + + chdir(oldwd); + // change working dir back to original + } else + moveMode = kCreateLink; + // fall back to absolute link mode + } + + if (moveMode == kCreateLink) + err = destDir->CreateSymLink(name, path.Path(), &link); + + if (err == B_UNSUPPORTED) + throw FailWithAlert(err, "The target disk does not support creating links.", NULL); + + FailWithAlert::FailOnError(err, "Error creating link to \"%s\".", ref.name); + + if (loc && loc != (BPoint *)-1) + link.WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo, sizeof(PoseInfo)); + + BNodeInfo nodeInfo(&link); + nodeInfo.SetType(B_LINK_MIMETYPE); + return B_OK; + } + + // if move is on same volume don't copy + if (statbuf.st_dev == destNode.device && moveMode != kCopySelectionTo + && moveMode != kDuplicateSelection) { + + // for "Move" the size for status is always 1 - since file + // size is irrelevant when simply moving to a new folder + + thread_id thread = find_thread(NULL); + if (gStatusWindow && gStatusWindow->HasStatus(thread)) + gStatusWindow->UpdateStatus(thread, ref.name, 1); + + MoveError::FailOnError(entry->MoveTo(destDir, newName)); + } else { + TrackerCopyLoopControl loopControl(find_thread(NULL)); + + bool makeOriginalName = (moveMode == kDuplicateSelection); + if (S_ISDIR(statbuf.st_mode)) + CopyFolder(entry, destDir, &loopControl, loc, makeOriginalName, undo); + else + CopyFile(entry, &statbuf, destDir, &loopControl, loc, makeOriginalName, undo); + } + } catch (status_t error) { + // no alert, was already taken care of before + return error; + } catch (MoveError error) { + BString errorString; + errorString << "Error moving \"" << ref.name << '"'; + (new BAlert("", errorString.String(), "OK", 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); + return error.fError; + } catch (FailWithAlert error) { + char buffer[256]; + if (error.fName) + sprintf(buffer, error.fString, error.fName); + else + strcpy(buffer, error.fString); + (new BAlert("", buffer, "OK", 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); + + return error.fError; + } + + return B_OK; +} + + +void +FSDuplicate(BObjectList *srcList, BList *pointList) +{ + LaunchInNewThread("DupTask", B_NORMAL_PRIORITY, MoveTask, srcList, (BEntry *)NULL, + pointList, kDuplicateSelection); +} + + +#if 0 +status_t +FSCopyFolder(BEntry *srcEntry, BDirectory *destDir, CopyLoopControl *loopControl, + BPoint *loc, bool makeOriginalName) +{ + try { + CopyFolder(srcEntry, destDir, loopControl, loc, makeOriginalName); + } catch (status_t error) { + return error; + } + + return B_OK; +} +#endif + + +status_t +FSCopyAttributesAndStats(BNode *srcNode, BNode *destNode) +{ + char *buffer = new char[1024]; + + // copy the attributes + srcNode->RewindAttrs(); + char name[256]; + while (srcNode->GetNextAttrName(name) == B_OK) { + attr_info info; + if (srcNode->GetAttrInfo(name, &info) != B_OK) + continue; + + attr_info dest_info; + if (destNode->GetAttrInfo(name, &dest_info) == B_OK) + continue; + + ssize_t bytes; + ssize_t numToRead = (ssize_t)info.size; + for (off_t offset = 0; numToRead > 0; offset += bytes) { + size_t chunkSize = (size_t)numToRead; + if (chunkSize > 1024) + chunkSize = 1024; + + bytes = srcNode->ReadAttr(name, info.type, offset, buffer, chunkSize); + + if (bytes <= 0) + break; + + destNode->WriteAttr(name, info.type, offset, buffer, (size_t)bytes); + + numToRead -= bytes; + } + } + delete[] buffer; + + // copy the file stats + struct stat srcStat; + srcNode->GetStat(&srcStat); + destNode->SetPermissions(srcStat.st_mode); + destNode->SetOwner(srcStat.st_uid); + destNode->SetGroup(srcStat.st_gid); + destNode->SetModificationTime(srcStat.st_mtime); + destNode->SetCreationTime(srcStat.st_crtime); + + return B_OK; +} + + +#if 0 +status_t +FSCopyFile(BEntry* srcFile, StatStruct *srcStat, BDirectory* destDir, + CopyLoopControl *loopControl, BPoint *loc, bool makeOriginalName) +{ + try { + CopyFile(srcFile, srcStat, destDir, loopControl, loc, makeOriginalName); + } catch (status_t error) { + return error; + } + + return B_OK; +} +#endif + + +static status_t +MoveEntryToTrash(BEntry *entry, BPoint *loc, Undo &undo) +{ + BDirectory trash_dir; + entry_ref ref; + status_t result = entry->GetRef(&ref); + if (result != B_OK) + return result; + + node_ref nodeRef; + result = entry->GetNodeRef(&nodeRef); + if (result != B_OK) + return result; + + StatStruct statbuf; + result = entry->GetStat(&statbuf); + if (entry->GetStat(&statbuf) != B_OK) + return result; + + // if it's a directory close the window and any child dir windows + if (S_ISDIR(statbuf.st_mode)) { + BDirectory dir(entry); + + // if it's a volume, try to unmount + if (dir.IsRootDirectory()) { + BVolume volume(nodeRef.device); + BVolume boot; + + BVolumeRoster().GetBootVolume(&boot); + if (volume == boot) { + char name[B_FILE_NAME_LENGTH]; + volume.GetName(name); + char buffer[256]; + sprintf(buffer, "Cannot unmount the boot volume \"%s\".", name); + (new BAlert("", buffer, "Cancel", 0, 0, + B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); + } else { + BMessage message(kUnmountVolume); + message.AddInt32("device_id", volume.Device()); + be_app->PostMessage(&message); + } + return B_OK; + } + + // get trash directory on same volume as item being moved + result = FSGetTrashDir(&trash_dir, nodeRef.device); + if (result != B_OK) + return result; + + // check hierarchy before moving + BEntry trashEntry; + trash_dir.GetEntry(&trashEntry); + + if (dir == trash_dir || dir.Contains(&trashEntry)) { + (new BAlert("", "You cannot put the Trash, home or Desktop " + "directory into the trash.", "OK", 0, 0, + B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); + + // return no error so we don't get two dialogs + return B_OK; + } + + BMessage message(kCloseWindowAndChildren); + + node_ref parentNode; + parentNode.device = statbuf.st_dev; + parentNode.node = statbuf.st_ino; + message.AddData("node_ref", B_RAW_TYPE, &parentNode, sizeof(node_ref)); + be_app->PostMessage(&message); + } else { + // get trash directory on same volume as item being moved + result = FSGetTrashDir(&trash_dir, nodeRef.device); + if (result != B_OK) + return result; + } + + // make sure name doesn't conflict with anything in trash already + char name[B_FILE_NAME_LENGTH]; + strcpy(name, ref.name); + if (trash_dir.Contains(name)) { + FSMakeOriginalName(name, &trash_dir, " copy"); + undo.UpdateEntry(entry, name); + } + + BNode *src_node = 0; + if (loc && loc != (BPoint *)-1 + && (src_node = GetWritableNode(entry, &statbuf)) != 0) { + trash_dir.GetStat(&statbuf); + PoseInfo poseInfo; + poseInfo.fInvisible = false; + poseInfo.fInitedDirectory = statbuf.st_ino; + poseInfo.fLocation = *loc; + src_node->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo, + sizeof(poseInfo)); + delete src_node; + } + + BNode node(entry); + BPath path; + // Get path of entry before it's moved to the trash + // and write it to the file as an attribute + if (node.InitCheck() == B_OK && entry->GetPath(&path) == B_OK) { + BString originalPath(path.Path()); + node.WriteAttrString(kAttrOriginalPath, &originalPath); + } + + MoveItem(entry, &trash_dir, loc, kMoveSelectionTo, name, undo); + return B_OK; +} + + +ConflictCheckResult +PreFlightNameCheck(BObjectList *srcList, const BDirectory *destDir, + int32 *collisionCount) +{ + + // count the number of name collisions in dest folder + *collisionCount = 0; + + int32 count = srcList->CountItems(); + for (int32 i = 0; i < count; i++) { + entry_ref *srcRef = srcList->ItemAt(i); + BEntry entry(srcRef); + BDirectory parent; + entry.GetParent(&parent); + + if (parent != *destDir) { + if (destDir->Contains(srcRef->name)) + (*collisionCount)++; + } + } + + // prompt user only if there is more than one collision, otherwise the + // single collision case will be handled as a "Prompt" case by CheckName + if (*collisionCount > 1) { + entry_ref *srcRef = (entry_ref*)srcList->FirstItem(); + + StatStruct statbuf; + destDir->GetStat(&statbuf); + + const char *verb = (srcRef->device == statbuf.st_dev) ? "moving" : "copying"; + char replaceMsg[256]; + sprintf(replaceMsg, kReplaceManyStr, verb, verb); + + switch ((new BAlert("", replaceMsg, "Cancel", "Prompt", "Replace All"))->Go()) { + case 0: + return kCanceled; + + case 1: + // user selected "Prompt" + return kPrompt; + + case 2: + // user selected "Replace All" + return kReplaceAll; + } + } + + return kNoConflicts; +} + + +void +FileStatToString(StatStruct *stat, char *buffer, int32 length) +{ + tm timeData; + localtime_r(&stat->st_mtime, &timeData); + + sprintf(buffer, "\n\t(%Ld bytes, ", stat->st_size); + uint32 pos = strlen(buffer); + strftime(buffer + pos, length - pos,"%b %d %Y, %I:%M:%S %p)", &timeData); +} + + +status_t +CheckName(uint32 moveMode, const BEntry *sourceEntry, const BDirectory *destDir, + bool multipleCollisions, ConflictCheckResult &replaceAll) +{ + if (moveMode == kDuplicateSelection) + // when duplicating, we will never have a conflict + return B_OK; + + // see if item already exists in destination dir + status_t err = B_OK; + char name[B_FILE_NAME_LENGTH]; + sourceEntry->GetName(name); + bool sourceIsDirectory = sourceEntry->IsDirectory(); + + BDirectory srcDirectory; + if (sourceIsDirectory) { + srcDirectory.SetTo(sourceEntry); + BEntry destEntry; + destDir->GetEntry(&destEntry); + + if (moveMode != kCreateLink + && moveMode != kCreateRelativeLink + && (srcDirectory == *destDir || srcDirectory.Contains(&destEntry))) { + (new BAlert("", "You can't move a folder into itself " + "or any of its own sub-folders.", "OK", 0, 0, + B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); + return B_ERROR; + } + } + + if (FSIsTrashDir(sourceEntry)) { + (new BAlert("", "You can't move or copy the trash.", + "OK", 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); + return B_ERROR; + } + + BEntry entry; + if (destDir->FindEntry(name, &entry) != B_OK) + // no conflict, return + return B_OK; + + if (moveMode == kCreateLink || moveMode == kCreateRelativeLink) { + // if we are creating link in the same directory, the conflict will + // be handled later by giving the link a unique name + sourceEntry->GetParent(&srcDirectory); + + if (srcDirectory == *destDir) + return B_OK; + } + + bool destIsDir = entry.IsDirectory(); + // be sure not to replace the parent directory of the item being moved + if (destIsDir) { + BDirectory test_dir(&entry); + if (test_dir.Contains(sourceEntry)) { + (new BAlert("", "You can't replace a folder " + "with one of its sub-folders.", "OK", 0, 0, + B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); + return B_ERROR; + } + } + + if (moveMode != kCreateLink + && moveMode != kCreateRelativeLink + && destIsDir != sourceIsDirectory) { + // ensure user isn't trying to replace a file with folder or vice versa + (new BAlert("", sourceIsDirectory + ? "You cannot replace a file with a folder or a symbolic link." + : "You cannot replace a folder or a symbolic link with a file.", + "OK", 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); + return B_ERROR; + } + + if (replaceAll != kReplaceAll) { + // prompt user to determine whether to replace or not + + char replaceMsg[512]; + + if (moveMode == kCreateLink || moveMode == kCreateRelativeLink) + sprintf(replaceMsg, kSymLinkReplaceStr, name); + else if (sourceEntry->IsDirectory()) + sprintf(replaceMsg, kDirectoryReplaceStr, name, + moveMode == kMoveSelectionTo ? "moving" : "copying"); + else { + char sourceBuffer[96], destBuffer[96]; + StatStruct statBuffer; + + if (!sourceEntry->IsDirectory() && sourceEntry->GetStat(&statBuffer) == B_OK) + FileStatToString(&statBuffer, sourceBuffer, 96); + else + sourceBuffer[0] = '\0'; + + if (!entry.IsDirectory() && entry.GetStat(&statBuffer) == B_OK) + FileStatToString(&statBuffer, destBuffer, 96); + else + destBuffer[0] = '\0'; + + sprintf(replaceMsg, kReplaceStr, name, destBuffer, name, sourceBuffer, + moveMode == kMoveSelectionTo ? "moving" : "copying"); + } + + // special case single collision (don't need Replace All shortcut) + BAlert *alert; + if (multipleCollisions) + alert = new BAlert("", replaceMsg, "Skip", "Replace All", + "Replace"); + else + alert = new BAlert("", replaceMsg, "Cancel", "Replace"); + + switch (alert->Go()) { + case 0: // user selected "Cancel" or "Skip" + replaceAll = kCanceled; + return B_ERROR; + + case 1: // user selected "Replace" or "Replace All" + replaceAll = kReplaceAll; + // doesn't matter which since a single + // collision "Replace" is equivalent to a + // "Replace All" + break; + } + } + + // delete destination item + if (destIsDir) { + TrackerCopyLoopControl loopControl(find_thread(NULL)); + err = FSDeleteFolder(&entry, &loopControl, false); + } else + err = entry.Remove(); + + if (err != B_OK) { + BString error; + error << "There was a problem trying to replace \"" + << name << "\". The item might be open or busy."; + (new BAlert("", error.String(), "Cancel", 0, 0, + B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); + } + + return err; +} + + +status_t +FSDeleteFolder(BEntry *dir_entry, CopyLoopControl *loopControl, bool update_status, + bool delete_top_dir, bool upateFileNameInStatus) +{ + entry_ref ref; + BEntry entry; + BDirectory dir; + status_t err; + + dir.SetTo(dir_entry); + dir.Rewind(); + + // loop through everything in folder and delete it, skipping trouble files + for (;;) { + if (dir.GetNextEntry(&entry) != B_OK) + break; + + entry.GetRef(&ref); + + if (loopControl->CheckUserCanceled()) + return kTrashCanceled; + + if (entry.IsDirectory()) + err = FSDeleteFolder(&entry, loopControl, update_status, true, + upateFileNameInStatus); + else { + err = entry.Remove(); + if (update_status) + loopControl->UpdateStatus(upateFileNameInStatus ? ref.name : "", ref, 1, true); + } + + if (err == kTrashCanceled) + return kTrashCanceled; + else if (err == B_OK) + dir.Rewind(); + else + loopControl->FileError(kFileDeleteErrorString, ref.name, err, false); + } + + if (loopControl->CheckUserCanceled()) + return kTrashCanceled; + + dir_entry->GetRef(&ref); + + if (update_status && delete_top_dir) + loopControl->UpdateStatus(NULL, ref, 1); + + if (delete_top_dir) + return dir_entry->Remove(); + else + return B_OK; +} + + +void +FSMakeOriginalName(BString &string, const BDirectory *destDir, const char *suffix) +{ + if (!destDir->Contains(string.String())) + return; + + FSMakeOriginalName(string.LockBuffer(B_FILE_NAME_LENGTH), + const_cast(destDir), suffix ? suffix : " copy"); + string.UnlockBuffer(); +} + + +void +FSMakeOriginalName(char *name, BDirectory *destDir, const char *suffix) +{ + char root[B_FILE_NAME_LENGTH]; + char copybase[B_FILE_NAME_LENGTH]; + char temp_name[B_FILE_NAME_LENGTH + 10]; + int32 fnum; + + // is this name already original? + if (!destDir->Contains(name)) + return; + + // Determine if we're copying a 'copy'. This algorithm isn't perfect. + // If you're copying a file whose REAL name ends with 'copy' then + // this method will return " 1", not " copy" + + // However, it will correctly handle file that contain 'copy' + // elsewhere in their name. + + bool copycopy = false; // are we copying a copy? + int32 len = (int32)strlen(name); + char *p = name + len - 1; // get pointer to end os name + + // eat up optional numbers (if were copying " copy 34") + while ((p > name) && isdigit(*p)) + p--; + + // eat up optional spaces + while ((p > name) && isspace(*p)) + p--; + + // now look for the phrase " copy" + if (p > name) { + // p points to the last char of the word. For example, 'y' in 'copy' + + if ((p - 4 > name) && (strncmp(p - 4, suffix, 5) == 0)) { + // we found 'copy' in the right place. + // so truncate after 'copy' + *(p + 1) = '\0'; + copycopy = true; + + // save the 'root' name of the file, for possible later use. + // that is copy everything but trailing " copy". Need to + // NULL terminate after copy + strncpy(root, name, (uint32)((p - name) - 4)); + root[(p - name) - 4] = '\0'; + } + } + + if (!copycopy) { + /* + The name can't be longer than B_FILE_NAME_LENGTH. + The algoritm adds " copy XX" to the name. That's 8 characters. + B_FILE_NAME_LENGTH already accounts for NULL termination so we + don't need to save an extra char at the end. + */ + if (strlen(name) > B_FILE_NAME_LENGTH - 8) { + // name is too long - truncate it! + name[B_FILE_NAME_LENGTH - 8] = '\0'; + } + + strcpy(root, name); // save root name + strcat(name, suffix); + } + + strcpy(copybase, name); + + // if name already exists then add a number + fnum = 1; + strcpy(temp_name, name); + while (destDir->Contains(temp_name)) { + sprintf(temp_name, "%s %ld", copybase, ++fnum); + + if (strlen(temp_name) > (B_FILE_NAME_LENGTH - 1)) { + /* + The name has grown too long. Maybe we just went from + " copy 9" to " copy 10" and that extra + character was too much. The solution is to further + truncate the 'root' name and continue. + ??? should we reset fnum or not ??? + */ + root[strlen(root) - 1] = '\0'; + sprintf(temp_name, "%s%s %ld", root, suffix, fnum); + } + } + + ASSERT((strlen(temp_name) <= (B_FILE_NAME_LENGTH - 1))); + strcpy(name, temp_name); +} + + +status_t +FSRecursiveCalcSize(BInfoWindow *wind, BDirectory *dir, off_t *running_size, + int32 *fileCount, int32 *dirCount) +{ + thread_id tid = find_thread(NULL); + + dir->Rewind(); + BEntry entry; + while (dir->GetNextEntry(&entry) == B_OK) { + + // be sure window hasn't closed + if (wind && wind->StopCalc()) + return B_OK; + + if (gStatusWindow && gStatusWindow->CheckCanceledOrPaused(tid)) + return kUserCanceled; + + StatStruct statbuf; + entry.GetStat(&statbuf); + + if (S_ISDIR(statbuf.st_mode)) { + BDirectory subdir(&entry); + (*dirCount)++; + (*running_size) += 1024; + status_t result; + if ((result = FSRecursiveCalcSize(wind, &subdir, running_size, + fileCount, dirCount)) != B_OK) + return result; + } else { + (*fileCount)++; + (*running_size) += statbuf.st_size + 1024; // Add to compensate + // for attributes. + } + } + return B_OK; +} + + +status_t +CalcItemsAndSize(BObjectList *refList, int32 *totalCount, off_t *totalSize) +{ + int32 fileCount = 0; + int32 dirCount = 0; + + thread_id tid = find_thread(NULL); + + int32 num_items = refList->CountItems(); + for (int32 i = 0; i < num_items; i++) { + entry_ref *ref = refList->ItemAt(i); + BEntry entry(ref); + StatStruct statbuf; + entry.GetStat(&statbuf); + + if (gStatusWindow && gStatusWindow->CheckCanceledOrPaused(tid)) + return kUserCanceled; + + if (S_ISDIR(statbuf.st_mode)) { + BDirectory dir(&entry); + dirCount++; + (*totalSize) += 1024; + status_t result; + if ((result = FSRecursiveCalcSize(NULL, &dir, totalSize, &fileCount, + &dirCount)) != B_OK) + return result; + } else { + fileCount++; + (*totalSize) += statbuf.st_size + 1024; + } + } + + *totalCount += (fileCount + dirCount); + return B_OK; +} + + +status_t +FSGetTrashDir(BDirectory *trash_dir, dev_t dev) +{ + + BVolume volume(dev); + status_t result = volume.InitCheck(); + if (result != B_OK) + return result; + + BPath path; + result = find_directory(B_TRASH_DIRECTORY, &path, true, &volume); + if (result != B_OK) + return result; + + result = trash_dir->SetTo(path.Path()); + if (result != B_OK) + return result; + + // make trash invisible + attr_info a_info; + if (trash_dir->GetAttrInfo(kAttrPoseInfo, &a_info) != B_OK) { + + StatStruct sbuf; + trash_dir->GetStat(&sbuf); + + // move trash to bottom left of main screen initially + BScreen screen(B_MAIN_SCREEN_ID); + BRect scrn_frame = screen.Frame(); + + PoseInfo poseInfo; + poseInfo.fInvisible = false; + poseInfo.fInitedDirectory = sbuf.st_ino; + poseInfo.fLocation = BPoint(scrn_frame.left + 20, scrn_frame.bottom - 60); + trash_dir->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo, + sizeof(PoseInfo)); + } + + return B_OK; +} + + +status_t +FSGetDeskDir(BDirectory *deskDir, dev_t dev) +{ + BVolume volume(dev); + status_t result = volume.InitCheck(); + if (result != B_OK) + return result; + + BPath path; + result = find_directory(B_DESKTOP_DIRECTORY, &path, true, &volume); + if (result != B_OK) + return result; + + result = deskDir->SetTo(path.Path()); + if (result != B_OK) + return result; + + // make desktop fInvisible + PoseInfo poseInfo; + poseInfo.fInvisible = true; + poseInfo.fInitedDirectory = -1LL; + deskDir->WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &poseInfo, sizeof(PoseInfo)); + + size_t size; + const void* data = GetTrackerResources()-> + LoadResource('ICON', kResDeskIcon, &size); + + if (data) + deskDir->WriteAttr(kAttrLargeIcon, B_COLOR_8_BIT_TYPE, 0, data, size); + + data = GetTrackerResources()-> + LoadResource('MICN', kResDeskIcon, &size); + + if (data) + deskDir->WriteAttr(kAttrMiniIcon, B_COLOR_8_BIT_TYPE, 0, data, size); + + return B_OK; +} + + +status_t +FSGetBootDeskDir(BDirectory *deskDir) +{ + BVolume bootVol; + BVolumeRoster().GetBootVolume(&bootVol); + BPath path; + + status_t result = find_directory(B_DESKTOP_DIRECTORY, &path, true, &bootVol); + if (result != B_OK) + return result; + + return deskDir->SetTo(path.Path()); +} + + +static bool +FSIsDirFlavor(const BEntry *entry, directory_which directoryType) +{ + StatStruct dir_stat; + StatStruct entry_stat; + BVolume volume; + BPath path; + + if (entry->GetStat(&entry_stat) != B_OK) + return false; + + if (volume.SetTo(entry_stat.st_dev) != B_OK) + return false; + + if (find_directory(directoryType, &path, false, &volume) != B_OK) + return false; + + stat(path.Path(), &dir_stat); + + return dir_stat.st_ino == entry_stat.st_ino + && dir_stat.st_dev == entry_stat.st_dev; +} + + +bool +FSIsPrintersDir(const BEntry *entry) +{ + return FSIsDirFlavor(entry, B_USER_PRINTERS_DIRECTORY); +} + + +bool +FSIsTrashDir(const BEntry *entry) +{ + return FSIsDirFlavor(entry, B_TRASH_DIRECTORY); +} + + +bool +FSIsDeskDir(const BEntry *entry, dev_t device) +{ + BVolume volume(device); + status_t result = volume.InitCheck(); + if (result != B_OK) + return false; + + BPath path; + result = find_directory(B_DESKTOP_DIRECTORY, &path, true, &volume); + if (result != B_OK) + return false; + + BEntry entryToCompare(path.Path()); + return entryToCompare == *entry; +} + + +bool +FSIsDeskDir(const BEntry *entry) +{ + entry_ref ref; + if (entry->GetRef(&ref) != B_OK) + return false; + + return FSIsDeskDir(entry, ref.device); +} + + +bool +FSIsSystemDir(const BEntry *entry) +{ + return FSIsDirFlavor(entry, B_BEOS_SYSTEM_DIRECTORY); +} + + +bool +FSIsBeOSDir(const BEntry *entry) +{ + return FSIsDirFlavor(entry, B_BEOS_DIRECTORY); +} + + +bool +FSIsHomeDir(const BEntry *entry) +{ + return FSIsDirFlavor(entry, B_USER_DIRECTORY); +} + + +bool +DirectoryMatchesOrContains(const BEntry *entry, directory_which which) +{ + BPath path; + if (find_directory(which, &path, false, NULL) != B_OK) + return false; + + BEntry dirEntry(path.Path()); + if (dirEntry.InitCheck() != B_OK) + return false; + + if (dirEntry == *entry) + // root level match + return true; + + BDirectory dir(&dirEntry); + return dir.Contains(entry); +} + + +bool +DirectoryMatchesOrContains(const BEntry *entry, const char *additionalPath, + directory_which which) +{ + BPath path; + if (find_directory(which, &path, false, NULL) != B_OK) + return false; + + path.Append(additionalPath); + BEntry dirEntry(path.Path()); + if (dirEntry.InitCheck() != B_OK) + return false; + + if (dirEntry == *entry) + // root level match + return true; + + BDirectory dir(&dirEntry); + return dir.Contains(entry); +} + + +bool +DirectoryMatches(const BEntry *entry, directory_which which) +{ + BPath path; + if (find_directory(which, &path, false, NULL) != B_OK) + return false; + + BEntry dirEntry(path.Path()); + if (dirEntry.InitCheck() != B_OK) + return false; + + return dirEntry == *entry; +} + + +bool +DirectoryMatches(const BEntry *entry, const char *additionalPath, directory_which which) +{ + BPath path; + if (find_directory(which, &path, false, NULL) != B_OK) + return false; + + path.Append(additionalPath); + BEntry dirEntry(path.Path()); + if (dirEntry.InitCheck() != B_OK) + return false; + + return dirEntry == *entry; +} + + +extern status_t +FSFindTrackerSettingsDir(BPath *path, bool autoCreate) +{ + status_t result = find_directory (B_USER_SETTINGS_DIRECTORY, path, autoCreate); + if (result != B_OK) + return result; + + path->Append("Tracker"); + + return mkdir(path->Path(), 0777) ? B_OK : errno; +} + + +bool +FSInTrashDir(const entry_ref *ref) +{ + BEntry entry(ref); + if (entry.InitCheck() != B_OK) + return false; + + BDirectory trashDir; + if (FSGetTrashDir(&trashDir, ref->device) != B_OK) + return false; + + return trashDir.Contains(&entry); +} + + +void +FSEmptyTrash() +{ + if (find_thread("_tracker_empty_trash_") == B_NAME_NOT_FOUND) + resume_thread(spawn_thread(empty_trash, "_tracker_empty_trash_", + B_NORMAL_PRIORITY, NULL)); +} + + +status_t +empty_trash(void *) +{ + BVolumeRoster roster; + BVolume volume; + BEntry entry; + BDirectory trash_dir; + entry_ref ref; + thread_id tid; + status_t err; + int32 totalCount; + off_t totalSize; + BObjectList srcList; + + // empty trash on all mounted volumes + err = B_OK; + + tid = find_thread(NULL); + if (gStatusWindow) + gStatusWindow->CreateStatusItem(tid, kTrashState); + + // calculate the sum total of all items on all volumes in trash + totalCount = 0; + totalSize = 0; + + roster.Rewind(); + while (roster.GetNextVolume(&volume) == B_OK) { + + if (volume.IsReadOnly() || !volume.IsPersistent()) + continue; + + if (FSGetTrashDir(&trash_dir, volume.Device()) != B_OK) + continue; + + trash_dir.GetEntry(&entry); + entry.GetRef(&ref); + srcList.AddItem(&ref); + err = CalcItemsAndSize(&srcList, &totalCount, &totalSize); + if (err != B_OK) + break; + + srcList.MakeEmpty(); + + // don't count trash directory itself + totalCount--; + } + + if (err == B_OK) { + if (gStatusWindow) + gStatusWindow->InitStatusItem(tid, totalCount, totalCount); + + roster.Rewind(); + while (roster.GetNextVolume(&volume) == B_OK) { + TrackerCopyLoopControl loopControl(tid); + + if (volume.IsReadOnly() || !volume.IsPersistent()) + continue; + + if (FSGetTrashDir(&trash_dir, volume.Device()) != B_OK) + continue; + + trash_dir.GetEntry(&entry); + err = FSDeleteFolder(&entry, &loopControl, true, false); + } + } + + if (err != B_OK && err != kTrashCanceled && err != kUserCanceled) + (new BAlert("", "Error emptying Trash!", "OK", NULL, NULL, + B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); + + if (gStatusWindow) + gStatusWindow->RemoveStatusItem(find_thread(NULL)); + + return B_OK; +} + + +status_t +_DeleteTask(BObjectList *list, bool confirm) +{ + if (confirm) { + bool dontMoveToTrash = TrackerSettings().DontMoveFilesToTrash(); + + if (!dontMoveToTrash) { + BAlert *alert = new BAlert("", kDeleteConfirmationStr, + "Cancel", "Move to Trash", "Delete", B_WIDTH_AS_USUAL, B_OFFSET_SPACING, + B_WARNING_ALERT); + + alert->SetShortcut(0, B_ESCAPE); + alert->SetShortcut(1, 'm'); + alert->SetShortcut(2, 'd'); + + switch (alert->Go()) { + case 0: + delete list; + return B_OK; + case 1: + FSMoveToTrash(list, NULL, false); + return B_OK; + } + } else { + BAlert *alert = new BAlert("", kDeleteConfirmationStr, + "Cancel", "Delete", NULL, B_WIDTH_AS_USUAL, B_OFFSET_SPACING, + B_WARNING_ALERT); + + alert->SetShortcut(0, B_ESCAPE); + alert->SetShortcut(1, 'd'); + + if (!alert->Go()) { + delete list; + return B_OK; + } + } + } + + thread_id thread = find_thread(NULL); + if (gStatusWindow) + gStatusWindow->CreateStatusItem(thread, kDeleteState); + + // calculate the sum total of all items on all volumes in trash + int32 totalItems = 0; + int64 totalSize = 0; + + status_t err = CalcItemsAndSize(list, &totalItems, &totalSize); + if (err == B_OK) { + if (gStatusWindow) + gStatusWindow->InitStatusItem(thread, totalItems, totalItems); + + int32 count = list->CountItems(); + TrackerCopyLoopControl loopControl(thread); + for (int32 index = 0; index < count; index++) { + entry_ref ref(*list->ItemAt(index)); + BEntry entry(&ref); + loopControl.UpdateStatus(ref.name, ref, 1, true); + if (entry.IsDirectory()) + err = FSDeleteFolder(&entry, &loopControl, true, true, true); + else + err = entry.Remove(); + } + + if (err != kTrashCanceled && err != kUserCanceled && err != B_OK) + (new BAlert("", "Error Deleting items", "OK", NULL, NULL, + B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); + } + if (gStatusWindow) + gStatusWindow->RemoveStatusItem(find_thread(NULL)); + + delete list; + + return B_OK; +} + +status_t +FSRecursiveCreateFolder(BPath path) +{ + BEntry entry(path.Path()); + if (entry.InitCheck() != B_OK) { + BPath parentPath; + status_t err = path.GetParent(&parentPath); + if (err != B_OK) + return err; + + err = FSRecursiveCreateFolder(parentPath); + if (err != B_OK) + return err; + } + + entry.SetTo(path.Path()); + if (entry.Exists()) + return B_FILE_EXISTS; + else { + char name[B_FILE_NAME_LENGTH]; + BDirectory parent; + + entry.GetParent(&parent); + entry.GetName(name); + parent.CreateDirectory(name, NULL); + } + + return B_OK; +} + +status_t +_RestoreTask(BObjectList *list) +{ + thread_id thread = find_thread(NULL); + if (gStatusWindow) + gStatusWindow->CreateStatusItem(thread, kRestoreFromTrashState); + + // calculate the sum total of all items that will be restored + int32 totalItems = 0; + int64 totalSize = 0; + + status_t err = CalcItemsAndSize(list, &totalItems, &totalSize); + if (err == B_OK) { + if (gStatusWindow) + gStatusWindow->InitStatusItem(thread, totalItems, totalItems); + + int32 count = list->CountItems(); + TrackerCopyLoopControl loopControl(thread); + for (int32 index = 0; index < count; index++) { + entry_ref ref(*list->ItemAt(index)); + BEntry entry(&ref); + BPath originalPath; + + loopControl.UpdateStatus(ref.name, ref, 1, true); + + if (FSGetOriginalPath(&entry, &originalPath) != B_OK) + continue; + + BEntry originalEntry(originalPath.Path()); + BPath parentPath; + err = originalPath.GetParent(&parentPath); + if (err != B_OK) + continue; + BEntry parentEntry(parentPath.Path()); + + if (parentEntry.InitCheck() != B_OK || !parentEntry.Exists()) { + if (FSRecursiveCreateFolder(parentPath) == B_OK) { + originalEntry.SetTo(originalPath.Path()); + if (entry.InitCheck() != B_OK) + continue; + } + } + + if (!originalEntry.Exists()) { + BDirectory dir(parentPath.Path()); + if (dir.InitCheck() == B_OK) { + char leafName[B_FILE_NAME_LENGTH]; + originalEntry.GetName(leafName); + if (entry.MoveTo(&dir, leafName) == B_OK) { + BNode node(&entry); + if (node.InitCheck() == B_OK) + node.RemoveAttr(kAttrOriginalPath); + } + } + } + + err = loopControl.CheckUserCanceled(); + if (err != B_OK) + break; + } + } + if (gStatusWindow) + gStatusWindow->RemoveStatusItem(find_thread(NULL)); + + delete list; + + return err; +} + +void +FSCreateTrashDirs() +{ + BVolume volume; + BVolumeRoster roster; + + roster.Rewind(); + while (roster.GetNextVolume(&volume) == B_OK) { + + if (volume.IsReadOnly() || !volume.IsPersistent()) + continue; + + BPath path; + find_directory(B_DESKTOP_DIRECTORY, &path, true, &volume); + find_directory(B_TRASH_DIRECTORY, &path, true, &volume); + + BDirectory trashDir; + if (FSGetTrashDir(&trashDir, volume.Device()) == B_OK) { + size_t size; + const void* data = GetTrackerResources()-> + LoadResource('ICON', kResTrashIcon, &size); + if (data) { + trashDir.WriteAttr(kAttrLargeIcon, B_COLOR_8_BIT_TYPE, 0, + data, size); + } + data = GetTrackerResources()-> + LoadResource('MICN', kResTrashIcon, &size); + if (data) { + trashDir.WriteAttr(kAttrMiniIcon, B_COLOR_8_BIT_TYPE, 0, + data, size); + } + } + } +} + + +status_t +FSCreateNewFolder(const entry_ref *ref) +{ + node_ref node; + node.device = ref->device; + node.node = ref->directory; + + BDirectory dir(&node); + status_t result = dir.InitCheck(); + if (result != B_OK) + return result; + + // ToDo: is that really necessary here? + BString name(ref->name); + FSMakeOriginalName(name, &dir, "-"); + + BDirectory newDir; + result = dir.CreateDirectory(name.String(), &newDir); + if (result != B_OK) + return result; + + BNodeInfo nodeInfo(&newDir); + nodeInfo.SetType(B_DIR_MIMETYPE); + + return result; +} + + +status_t +FSCreateNewFolderIn(const node_ref *dirNode, entry_ref *newRef, + node_ref *newNode) +{ + BDirectory dir(dirNode); + status_t result = dir.InitCheck(); + if (result == B_OK) { + char name[B_FILE_NAME_LENGTH]; + strcpy(name, "New Folder"); + + int32 fnum = 1; + while (dir.Contains(name)) { + // if base name already exists then add a number + // ToDo: + // move this logic ot FSMakeOriginalName + if (++fnum > 9) + sprintf(name, "New Folder%ld", fnum); + else + sprintf(name, "New Folder %ld", fnum); + } + + BDirectory newDir; + result = dir.CreateDirectory(name, &newDir); + if (result == B_OK) { + BEntry entry; + newDir.GetEntry(&entry); + entry.GetRef(newRef); + entry.GetNodeRef(newNode); + + BNodeInfo nodeInfo(&newDir); + nodeInfo.SetType(B_DIR_MIMETYPE); + + // add undo item + NewFolderUndo undo(*newRef); + return B_OK; + } + } + + (new BAlert("", "Sorry, could not create a new folder.", "Cancel", 0, 0, + B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); + return result; +} + + +ReadAttrResult +ReadAttr(const BNode *node, const char *hostAttrName, const char *foreignAttrName, + type_code type, off_t offset, void *buffer, size_t length, + void (*swapFunc)(void *), bool isForeign) +{ + if (!isForeign && node->ReadAttr(hostAttrName, type, offset, buffer, length) == (ssize_t)length) + return kReadAttrNativeOK; + + // PRINT(("trying %s\n", foreignAttrName)); + // try the other endianness + if (node->ReadAttr(foreignAttrName, type, offset, buffer, length) != (ssize_t)length) + return kReadAttrFailed; + + // PRINT(("got %s\n", foreignAttrName)); + if (!swapFunc) + return kReadAttrForeignOK; + + (swapFunc)(buffer); + // run the endian swapper + + return kReadAttrForeignOK; +} + + +ReadAttrResult +GetAttrInfo(const BNode *node, const char *hostAttrName, const char *foreignAttrName, + type_code *type, size_t *size) +{ + attr_info info; + + if (node->GetAttrInfo(hostAttrName, &info) == B_OK) { + if (type) + *type = info.type; + if (size) + *size = (size_t)info.size; + + return kReadAttrNativeOK; + } + + if (node->GetAttrInfo(foreignAttrName, &info) == B_OK) { + if (type) + *type = info.type; + if (size) + *size = (size_t)info.size; + + return kReadAttrForeignOK; + } + return kReadAttrFailed; +} + +// launching code + +static status_t +TrackerOpenWith(const BMessage *refs) +{ + BMessage clone(*refs); + ASSERT(dynamic_cast(be_app)); + ASSERT(clone.what); + clone.AddInt32("launchUsingSelector", 0); + // runs the Open With window + be_app->PostMessage(&clone); + + return B_OK; +} + +static void +AsynchLaunchBinder(void (*func)(const entry_ref *, const BMessage *, bool on), + const entry_ref *entry, const BMessage *message, bool on) +{ + Thread::Launch(NewFunctionObject(func, entry, message, on), + B_NORMAL_PRIORITY, "LaunchTask"); +} + +static bool +SniffIfGeneric(const entry_ref *ref) +{ + BNode node(ref); + char type[B_MIME_TYPE_LENGTH]; + BNodeInfo info(&node); + if (info.GetType(type) == B_OK && strcasecmp(type, B_FILE_MIME_TYPE) != 0) + // already has a type and it's not octet stream + return false; + + BPath path(ref); + if (path.Path()) { + // force a mimeset + node.RemoveAttr(kAttrMIMEType); + update_mime_info(path.Path(), 0, 1, 1); + } + + return true; +} + +static void +SniffIfGeneric(const BMessage *refs) +{ + entry_ref ref; + for (int32 index = 0; ; index++) { + if (refs->FindRef("refs", index, &ref) != B_OK) + break; + SniffIfGeneric(&ref); + } +} + +static void +_TrackerLaunchAppWithDocuments(const entry_ref *appRef, const BMessage *refs, bool openWithOK) +{ + team_id team; + + status_t error = B_ERROR; + BString alertString; + + for (int32 mimesetIt = 0; ; mimesetIt++) { + error = be_roster->Launch(appRef, refs, &team); + if (error == B_ALREADY_RUNNING) + // app already running, not really an error + error = B_OK; + + if (error == B_OK) + break; + + if (mimesetIt > 0) + break; + + // failed to open, try mimesetting the refs and launching again + SniffIfGeneric(refs); + } + + if (error == B_OK) { + // close possible parent window, if specified + const node_ref *nodeToClose = 0; + int32 numBytes; + refs->FindData("nodeRefsToClose", B_RAW_TYPE, (const void **)&nodeToClose, &numBytes); + if (nodeToClose) + dynamic_cast(be_app)->CloseParent(*nodeToClose); + } else { + alertString << "Could not open \"" << appRef->name << "\" (" << strerror(error) << "). "; + if (refs && openWithOK) { + alertString << kFindAlternativeStr; + if ((new BAlert("", alertString.String(), "Cancel", "Find", 0, + B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go() == 1) + error = TrackerOpenWith(refs); + } else + (new BAlert("", alertString.String(), "Cancel", 0, 0, + B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); + } +} + +extern "C" char** environ; +extern "C" +#if !B_BEOS_VERSION_DANO +_IMPEXP_ROOT +#endif +status_t _kload_image_etc_(int argc, char **argv, char **envp, + char *buf, int bufsize); + + +static status_t +LoaderErrorDetails(const entry_ref *app, BString &details) +{ + BPath path; + BEntry appEntry(app, true); + status_t result = appEntry.GetPath(&path); + + if (result != B_OK) + return result; + + char *argv[2] = { const_cast(path.Path()), 0}; + +#ifdef __HAIKU__ + // ToDo: do this correctly! + result = load_image(1, (const char **)argv, (const char **)environ); + details.SetTo("ToDo: this is missing from Haiku"); +#else + result = _kload_image_etc_(1, argv, environ, details.LockBuffer(1024), 1024); + details.UnlockBuffer(); +#endif + return B_OK; +} + + +static void +_TrackerLaunchDocuments(const entry_ref */*doNotUse*/, const BMessage *refs, + bool openWithOK) +{ + BMessage copyOfRefs(*refs); + + entry_ref documentRef; + if (copyOfRefs.FindRef("refs", &documentRef) != B_OK) + // nothing to launch, we are done + return; + + status_t error = B_ERROR; + entry_ref app; + BMessage *refsToPass = NULL; + BString alertString; + const char *alternative = 0; + + for (int32 mimesetIt = 0; ; mimesetIt++) { + alertString = ""; + error = be_roster->FindApp(&documentRef, &app); + + if (error != B_OK && mimesetIt == 0) { + SniffIfGeneric(©OfRefs); + continue; + } + + if (error != B_OK) { + alertString << "Could not find an application to open \"" << documentRef.name + << "\" (" << strerror(error) << "). "; + if (openWithOK) + alternative = kFindApplicationStr; + + break; + } else { + BEntry appEntry(&app, true); + for (int32 index = 0;;) { + // remove the app itself from the refs received so we don't try + // to open ourselves + entry_ref ref; + if (copyOfRefs.FindRef("refs", index, &ref) != B_OK) + break; + + // deal with symlinks properly + BEntry documentEntry(&ref, true); + if (appEntry == documentEntry) { + PRINT(("stripping %s, app %s \n", ref.name, app.name)); + copyOfRefs.RemoveData("refs", index); + } else { + PRINT(("leaving %s, app %s \n", ref.name, app.name)); + index++; + } + } + + refsToPass = CountRefs(©OfRefs) > 0 ? ©OfRefs: 0; + team_id team; + error = be_roster->Launch(&app, refsToPass, &team); + if (error == B_ALREADY_RUNNING) + // app already running, not really an error + error = B_OK; + if (error == B_OK || mimesetIt != 0) + break; + + SniffIfGeneric(©OfRefs); + } + } + + if (error != B_OK && alertString.Length() == 0) { + BString loaderErrorString; + bool openedDocuments = true; + + if (!refsToPass) { + // we just double clicked the app itself, do not offer to + // find a handling app + openWithOK = false; + openedDocuments = false; + } + + if (error == B_LAUNCH_FAILED_EXECUTABLE && !refsToPass) { + alertString << "Could not open \"" << app.name + << "\". The file is mistakenly marked as executable. "; + + if (!openWithOK) { + // offer the possibility to change the permissions + + alertString << "\nShould this be fixed?"; + if ((new BAlert("", alertString.String(), "Cancel", "Proceed", 0, + B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go() == 1) { + BEntry entry(&documentRef); + mode_t permissions; + + error = entry.GetPermissions(&permissions); + if (error == B_OK) + error = entry.SetPermissions(permissions & ~(S_IXUSR | S_IXGRP | S_IXOTH)); + if (error == B_OK) { + // we updated the permissions, so let's try again + _TrackerLaunchDocuments(NULL, refs, false); + return; + } else { + alertString = "Could not update permissions of file \""; + alertString << app.name << "\". " << strerror(error); + } + } else + return; + } + + alternative = kFindApplicationStr; + } else if (error == B_LAUNCH_FAILED_APP_IN_TRASH) { + alertString << "Could not open \"" << documentRef.name + << "\" because application \"" << app.name << "\" is in the trash. "; + alternative = kFindAlternativeStr; + } else if (error == B_LAUNCH_FAILED_APP_NOT_FOUND) { + alertString << "Could not open \"" << documentRef.name << "\" " + << "(" << strerror(error) << "). "; + alternative = kFindAlternativeStr; + } else if (error == B_MISSING_SYMBOL + && LoaderErrorDetails(&app, loaderErrorString) == B_OK) { + alertString << "Could not open \"" << documentRef.name << "\" "; + if (openedDocuments) + alertString << "with application \"" << app.name << "\" "; + alertString << "(Missing symbol: " << loaderErrorString << "). \n"; + alternative = kFindAlternativeStr; + } else if (error == B_MISSING_LIBRARY + && LoaderErrorDetails(&app, loaderErrorString) == B_OK) { + alertString << "Could not open \"" << documentRef.name << "\" "; + if (openedDocuments) + alertString << "with application \"" << app.name << "\" "; + alertString << "(Missing library: " << loaderErrorString << "). \n"; + alternative = kFindAlternativeStr; + } else { + alertString << "Could not open \"" << documentRef.name + << "\" with application \"" << app.name << "\" (" << strerror(error) << "). "; + alternative = kFindAlternativeStr; + } + } + + if (error != B_OK) { + if (openWithOK) { + ASSERT(alternative); + alertString << alternative; + if ((new BAlert("", alertString.String(), "Cancel", "Find", 0, + B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go() == 1) + error = TrackerOpenWith(refs); + } else + (new BAlert("", alertString.String(), "Cancel", 0, 0, + B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); + } +} + +// the following three calls don't return any reasonable error codes, +// should fix that, making them void + +status_t +TrackerLaunch(const entry_ref *appRef, const BMessage *refs, bool async, bool openWithOK) +{ + if (!async) + _TrackerLaunchAppWithDocuments(appRef, refs, openWithOK); + else + AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments, appRef, refs, openWithOK); + + return B_OK; +} + +status_t +TrackerLaunch(const entry_ref *appRef, bool async) +{ + if (!async) + _TrackerLaunchAppWithDocuments(appRef, 0, false); + else + AsynchLaunchBinder(&_TrackerLaunchAppWithDocuments, appRef, 0, false); + + return B_OK; +} + +status_t +TrackerLaunch(const BMessage *refs, bool async, bool openWithOK) +{ + if (!async) + _TrackerLaunchDocuments(0, refs, openWithOK); + else + AsynchLaunchBinder(&_TrackerLaunchDocuments, 0, refs, openWithOK); + + return B_OK; +} + +status_t +LaunchBrokenLink(const char *signature, const BMessage *refs) +{ + // This call is to support a hacky workaround for double-clicking + // broken refs for cifs + be_roster->Launch(signature, const_cast(refs)); + return B_OK; +} + +// external launch calls; need to be robust, work if Tracker is not running + +#if !B_BEOS_VERSION_DANO +_IMPEXP_TRACKER +#endif +status_t +FSLaunchItem(const entry_ref *application, const BMessage *refsReceived, + bool async, bool openWithOK) +{ + return TrackerLaunch(application, refsReceived, async, openWithOK); +} + + +#if !B_BEOS_VERSION_DANO +_IMPEXP_TRACKER +#endif +status_t +FSOpenWith(BMessage *listOfRefs) +{ + status_t result = B_ERROR; + listOfRefs->what = B_REFS_RECEIVED; + + if (dynamic_cast(be_app)) + result = TrackerOpenWith(listOfRefs); + else + ASSERT(!"not yet implemented"); + + return result; +} + +// legacy calls, need for compatibility + +void +FSOpenWithDocuments(const entry_ref *executable, BMessage *documents) +{ + TrackerLaunch(executable, documents, true); + delete documents; +} + +status_t +FSLaunchUsing(const entry_ref *ref, BMessage *listOfRefs) +{ + BMessage temp(B_REFS_RECEIVED); + if (!listOfRefs) { + ASSERT(ref); + temp.AddRef("refs", ref); + listOfRefs = &temp; + } + FSOpenWith(listOfRefs); + return B_OK; +} + +status_t +FSLaunchItem(const entry_ref *ref, BMessage* message, int32, bool async) +{ + if (message) + message->what = B_REFS_RECEIVED; + + status_t result = TrackerLaunch(ref, message, async, true); + delete message; + return result; +} + + +void +FSLaunchItem(const entry_ref *ref, BMessage *message, int32 workspace) +{ + FSLaunchItem(ref, message, workspace, true); +} + +// Get the original path of an entry in the trash +status_t +FSGetOriginalPath(BEntry *entry, BPath *result) +{ + status_t err; + entry_ref ref; + err = entry->GetRef(&ref); + if (err != B_OK) + return err; + + // Only call the routine for entries in the trash + if (!FSInTrashDir(&ref)) + return B_ERROR; + + BNode node(entry); + BString originalPath; + if (node.ReadAttrString(kAttrOriginalPath, &originalPath) == B_OK) { + // We're in luck, the entry has the original path in an attribute + err = result->SetTo(originalPath.String()); + return err; + } + + // Iterate the parent directories to find one with + // the original path attribute + BEntry parent(*entry); + err = parent.InitCheck(); + if (err != B_OK) + return err; + + // walk up the directory structure until we find a node + // with original path attribute + do { + // move to the parent of this node + err = parent.GetParent(&parent); + if (err != B_OK) + return err; + + // return if we are at the root of the trash + if (FSIsTrashDir(&parent)) + return B_ENTRY_NOT_FOUND; + + // get the parent as a node + err = node.SetTo(&parent); + if (err != B_OK) + return err; + } while (node.ReadAttrString(kAttrOriginalPath, &originalPath) != B_OK); + + // Found the attribute, figure out there this file + // used to live, based on the successfully-read attribute + err = result->SetTo(originalPath.String()); + if (err != B_OK) + return err; + + BPath path, pathParent; + err = parent.GetPath(&pathParent); + if (err != B_OK) + return err; + err = entry->GetPath(&path); + if (err != B_OK) + return err; + result->Append(path.Path() + strlen(pathParent.Path()) + 1); + // compute the new path by appending the offset of + // the item we are locating, to the original path + // of the parent + return B_OK; +} + +directory_which +WellKnowEntryList::Match(const node_ref *node) +{ + const WellKnownEntry *result = MatchEntry(node); + if (result) + return result->which; + + return (directory_which)-1; +} + +const WellKnowEntryList::WellKnownEntry * +WellKnowEntryList::MatchEntry(const node_ref *node) +{ + if (!self) + self = new WellKnowEntryList(); + + return self->MatchEntryCommon(node); +} + +const WellKnowEntryList::WellKnownEntry * +WellKnowEntryList::MatchEntryCommon(const node_ref *node) +{ + uint32 count = entries.size(); + for (uint32 index = 0; index < count; index++) + if (*node == entries[index].node) + return &entries[index]; + + return NULL; +} + + +void +WellKnowEntryList::Quit() +{ + delete self; + self = NULL; +} + +void +WellKnowEntryList::AddOne(directory_which which, const char *name) +{ + BPath path; + if (find_directory(which, &path, true) != B_OK) + return; + + BEntry entry(path.Path()); + node_ref node; + if (entry.GetNodeRef(&node) != B_OK) + return; + + entries.push_back(WellKnownEntry(&node, which, name)); +} + +void +WellKnowEntryList::AddOne(directory_which which, directory_which base, + const char *extra, const char *name) +{ + BPath path; + if (find_directory(base, &path, true) != B_OK) + return; + + path.Append(extra); + BEntry entry(path.Path()); + node_ref node; + if (entry.GetNodeRef(&node) != B_OK) + return; + + entries.push_back(WellKnownEntry(&node, which, name)); +} + +void +WellKnowEntryList::AddOne(directory_which which, const char *path, const char *name) +{ + BEntry entry(path); + node_ref node; + if (entry.GetNodeRef(&node) != B_OK) + return; + + entries.push_back(WellKnownEntry(&node, which, name)); +} + + +WellKnowEntryList::WellKnowEntryList() +{ + AddOne(B_BEOS_DIRECTORY, "beos"); + AddOne((directory_which)B_BOOT_DISK, "/boot", "boot"); + AddOne(B_USER_DIRECTORY, "home"); + AddOne(B_BEOS_SYSTEM_DIRECTORY, "system"); + + AddOne(B_BEOS_FONTS_DIRECTORY, "fonts"); + AddOne(B_COMMON_FONTS_DIRECTORY, "fonts"); + AddOne(B_USER_FONTS_DIRECTORY, "fonts"); + + AddOne(B_BEOS_APPS_DIRECTORY, "apps"); + AddOne(B_APPS_DIRECTORY, "apps"); + AddOne((directory_which)B_USER_DESKBAR_APPS_DIRECTORY, B_USER_DESKBAR_DIRECTORY, + "Applications", "apps"); + + AddOne(B_BEOS_PREFERENCES_DIRECTORY, "preferences"); + AddOne(B_PREFERENCES_DIRECTORY, "preferences"); + AddOne((directory_which)B_USER_DESKBAR_PREFERENCES_DIRECTORY, B_USER_DESKBAR_DIRECTORY, + "Preferences", "preferences"); + + AddOne((directory_which)B_USER_MAIL_DIRECTORY, B_USER_DIRECTORY, "mail", "mail"); + + AddOne((directory_which)B_USER_QUERIES_DIRECTORY, B_USER_DIRECTORY, "queries", "queries"); + + + + AddOne(B_COMMON_DEVELOP_DIRECTORY, "develop"); + AddOne((directory_which)B_USER_DESKBAR_DEVELOP_DIRECTORY, B_USER_DESKBAR_DIRECTORY, + "Development", "develop"); + + AddOne(B_USER_CONFIG_DIRECTORY, "config"); + + AddOne((directory_which)B_USER_PEOPLE_DIRECTORY, B_USER_DIRECTORY, "people", "people"); + + AddOne((directory_which)B_USER_DOWNLOADS_DIRECTORY, B_USER_DIRECTORY, "Downloads", + "Downloads"); +} + +WellKnowEntryList *WellKnowEntryList::self = NULL; + +} diff --git a/src/kits/tracker/FSUtils.h b/src/kits/tracker/FSUtils.h new file mode 100644 index 0000000000..acb4288e9d --- /dev/null +++ b/src/kits/tracker/FSUtils.h @@ -0,0 +1,311 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef FS_UTILS_H +#define FS_UTILS_H + +#include +#include +#include +#include + +#include + +#include "Model.h" +#include "ObjectList.h" + +// Note - APIs/code in FSUtils.h and FSUtils.cpp is slated for a major cleanup +// -- in other words, you will find a lot of ugly cruft in here + +class BDirectory; +class BEntry; +class BList; +class BFile; + +namespace BPrivate { + +class BInfoWindow; + +class CopyLoopControl { + // controls the copy engine; may be overriden to specify how conflicts are + // handled, etc. + // Installer has it's own subclass + public: + virtual ~CopyLoopControl(); + virtual bool FileError(const char *message, const char *name, status_t error, + bool allowContinue) = 0; + // inform that a file error occurred while copying + // returns true if user decided to continue + + virtual void UpdateStatus(const char *name, entry_ref ref, int32 count, + bool optional = false) = 0; + + virtual bool CheckUserCanceled() = 0; + // returns true if canceled + + enum OverwriteMode { + kSkip, // do not replace, go to next entry + kReplace, // remove entry before copying new one + kMerge // for folders: leave existing folder, update contents leaving + // nonconflicting items + // for files: save original attributes on file. + }; + + virtual OverwriteMode OverwriteOnConflict(const BEntry *srcEntry, + const char *destName, const BDirectory *destDir, bool srcIsDir, + bool dstIsDir) = 0; + // override to always overwrite, never overwrite, let user decide, + // compare dates, etc. + + virtual bool SkipEntry(const BEntry *, bool file) = 0; + // override to prevent copying of a given file or directory + + virtual void ChecksumChunk(const char *block, size_t size); + // during a file copy, this is called every time a chunk of data + // is copied. Users may override to keep a running checksum. + + virtual bool ChecksumFile(const entry_ref *); + // This is called when a file is finished copying. Users of this + // class may override to verify that the checksum they've been + // computing in ChecksumChunk matches. If this returns true, + // the copy will continue. If false, if will abort. + + virtual bool SkipAttribute(const char *attributeName); + virtual bool PreserveAttribute(const char *attributeName); +}; + + +class TrackerCopyLoopControl : public CopyLoopControl { + // this is the Tracker copy - specific version of CopyLoopControl + public: + TrackerCopyLoopControl(thread_id); + virtual ~TrackerCopyLoopControl() {} + + virtual bool FileError(const char *message, const char *name, status_t error, + bool allowContinue); + // inform that a file error occurred while copying + // returns true if user decided to continue + + virtual void UpdateStatus(const char *name, entry_ref ref, int32 count, + bool optional = false); + + virtual bool CheckUserCanceled(); + // returns true if canceled + + virtual OverwriteMode OverwriteOnConflict(const BEntry *srcEntry, + const char *destName, const BDirectory *destDir, bool srcIsDir, + bool dstIsDir); + + virtual bool SkipEntry(const BEntry *, bool file); + // override to prevent copying of a given file or directory + + virtual bool SkipAttribute(const char *attributeName); + + private: + thread_id fThread; +}; + + +inline +TrackerCopyLoopControl::TrackerCopyLoopControl(thread_id thread) + : fThread(thread) +{ +} + +#define B_DESKTOP_DIR_NAME "Desktop" + +#if B_BEOS_VERSION_DANO +#define _IMPEXP_TRACKER +#endif +_IMPEXP_TRACKER status_t FSCopyAttributesAndStats(BNode *, BNode *); + +_IMPEXP_TRACKER void FSDuplicate(BObjectList *srcList, BList *pointList); +_IMPEXP_TRACKER void FSMoveToFolder(BObjectList *srcList, BEntry *, uint32 moveMode, + BList *pointList = NULL); +_IMPEXP_TRACKER void FSMakeOriginalName(char *name, BDirectory *destDir, const char *suffix); +_IMPEXP_TRACKER bool FSIsTrashDir(const BEntry *); +_IMPEXP_TRACKER bool FSIsPrintersDir(const BEntry *); +_IMPEXP_TRACKER bool FSIsDeskDir(const BEntry *); +_IMPEXP_TRACKER bool FSIsSystemDir(const BEntry *); +_IMPEXP_TRACKER bool FSIsBeOSDir(const BEntry *); +_IMPEXP_TRACKER bool FSIsHomeDir(const BEntry *); +_IMPEXP_TRACKER void FSMoveToTrash(BObjectList *srcList, BList *pointList = NULL, + bool async = true); + // Deprecated + +void FSDeleteRefList(BObjectList *, bool, bool confirm = true); +void FSDelete(entry_ref *, bool, bool confirm = true); +void FSRestoreRefList(BObjectList *list, bool async); + +_IMPEXP_TRACKER status_t FSLaunchItem(const entry_ref *application, const BMessage *refsReceived, + bool async, bool openWithOK); + // Preferred way of launching; only pass an actual application in , not + // a document; to open documents with the preferred app, pase 0 in and + // stuff all the document refs into + // Consider having silent mode that does not show alerts, just returns error code + +_IMPEXP_TRACKER status_t FSOpenWith(BMessage *listOfRefs); + // runs the Open With window; pas a list of refs + +_IMPEXP_TRACKER void FSEmptyTrash(); +_IMPEXP_TRACKER status_t FSCreateNewFolderIn(const node_ref *destDir, entry_ref *newRef, + node_ref *new_node); +_IMPEXP_TRACKER void FSCreateTrashDirs(); +_IMPEXP_TRACKER status_t FSGetTrashDir(BDirectory *trashDir, dev_t volume); +_IMPEXP_TRACKER status_t FSGetDeskDir(BDirectory *deskDir, dev_t volume); +_IMPEXP_TRACKER status_t FSRecursiveCalcSize(BInfoWindow *, BDirectory *, + off_t *runningSize, int32 *fileCount, int32 *dirCount); + +bool FSInTrashDir(const entry_ref *); + +// doesn't need to be exported +bool FSGetPoseLocation(const BNode *node, BPoint *point); +status_t FSSetPoseLocation(BEntry *entry, BPoint point); +status_t FSSetPoseLocation(ino_t destDirInode, BNode *destNode, BPoint point); +status_t FSGetBootDeskDir(BDirectory *deskDir); + +status_t FSGetOriginalPath(BEntry *entry, BPath *path); + +enum ReadAttrResult { + kReadAttrFailed, + kReadAttrNativeOK, + kReadAttrForeignOK +}; + +ReadAttrResult ReadAttr(const BNode *, const char *hostAttrName, const char *foreignAttrName, + type_code , off_t , void *, size_t , void (*swapFunc)(void *) = 0, + bool isForeign = false); + // Endian swapping ReadAttr call; endianness is determined by trying first the + // native attribute name, then the foreign one; an endian swapping function can + // be passed, if null data won't be swapped; if set the foreign endianness + // will be read directly without first trying the native one + +ReadAttrResult GetAttrInfo(const BNode *, const char *hostAttrName, const char *foreignAttrName, + type_code * = NULL, size_t * = NULL); + +status_t FSCreateNewFolder(const entry_ref *); +status_t FSRecursiveCreateFolder(const char *path); +void FSMakeOriginalName(BString &name, const BDirectory *destDir, const char *suffix = 0); + +status_t TrackerLaunch(const entry_ref *app, bool async); +status_t TrackerLaunch(const BMessage *refs, bool async, bool okToRunOpenWith = true); +status_t TrackerLaunch(const entry_ref *app, const BMessage *refs, bool async, + bool okToRunOpenWith = true); +status_t LaunchBrokenLink(const char *, const BMessage *); + +status_t FSFindTrackerSettingsDir(BPath *, bool autoCreate = true); + +bool FSIsDeskDir(const BEntry *, dev_t); + +bool ConfirmChangeIfWellKnownDirectory(const BEntry *entry, const char *action, + bool dontAsk = false, int32 *confirmedAlready = NULL); + +// Deprecated calls use newer calls above instead +_IMPEXP_TRACKER void FSLaunchItem(const entry_ref *, BMessage * = NULL, int32 workspace = -1); +_IMPEXP_TRACKER status_t FSLaunchItem(const entry_ref *, BMessage *, + int32 workspace, bool asynch); +_IMPEXP_TRACKER void FSOpenWithDocuments(const entry_ref *executableToLaunch, + BMessage *documentEntryRefs); +_IMPEXP_TRACKER status_t FSLaunchUsing(const entry_ref *ref, BMessage *listOfRefs); + + +// some extra directory_which values +// move these to FindDirectory.h +const uint32 B_USER_MAIL_DIRECTORY = 3500; +const uint32 B_USER_QUERIES_DIRECTORY = 3501; +const uint32 B_USER_PEOPLE_DIRECTORY = 3502; +const uint32 B_USER_DOWNLOADS_DIRECTORY = 3503; +const uint32 B_USER_DESKBAR_APPS_DIRECTORY = 3504; +const uint32 B_USER_DESKBAR_PREFERENCES_DIRECTORY = 3505; +const uint32 B_USER_DESKBAR_DEVELOP_DIRECTORY = 3506; + +const int32 B_BOOT_DISK = 10000000; + // map /boot into the directory_which enum for convenience + +class WellKnowEntryList { + // matches up names, id's and node_refs of well known entries in the + // system hierarchy + public: + struct WellKnownEntry { + WellKnownEntry(const node_ref *node, directory_which which, const char *name) + : + node(*node), + which(which), + name(name) + { + } + + // mwcc needs these explicitly to use vector + WellKnownEntry(const WellKnownEntry &clone) + : + node(clone.node), + which(clone.which), + name(clone.name) + { + } + + WellKnownEntry() + { + } + + node_ref node; + directory_which which; + const char *name; + }; + + static directory_which Match(const node_ref *); + static const WellKnownEntry *MatchEntry(const node_ref *); + static void Quit(); + + private: + const WellKnownEntry *MatchEntryCommon(const node_ref *); + WellKnowEntryList(); + void AddOne(directory_which, const char *name); + void AddOne(directory_which, const char *path, const char *name); + void AddOne(directory_which, directory_which base, const char *extension, + const char *name); + + std::vector entries; + static WellKnowEntryList *self; +}; + +#if B_BEOS_VERSION_DANO +#undef _IMPEXP_TRACKER +#endif + +} // namespace BPrivate + +using namespace BPrivate; + +#endif /* FS_UTILS_H */ diff --git a/src/kits/tracker/FavoritesConfig.cpp b/src/kits/tracker/FavoritesConfig.cpp new file mode 100644 index 0000000000..096ad5adc3 --- /dev/null +++ b/src/kits/tracker/FavoritesConfig.cpp @@ -0,0 +1,2297 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// *************************************************************************************** +// BeMenu configuration +// +// add temp item on dnd with new group +// save window location +// selection information +// last group showing +// recents +// auto scroll of contentsmenu +// default settings +// off screen drawing +// return/cancel in editingtext for name invokes name change +// after New Folder, scroll to selection +// move of items to folders +// change parent folder btn to be a NavMenu +// +// *************************************************************************************** + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Tracker +#include "Commands.h" +#include "IconCache.h" +#include "IconMenuItem.h" +#include "Model.h" +#include "ObjectList.h" +#include "tracker_private.h" +#include "Utilities.h" + +#include "FavoritesConfig.h" + + +enum { + kRemove = 'rmve', + kAdd, + kRecentDocs, + kRecentFolders, + kRecentApps, + kNewGroup, + kItemSelected, + kOpenItem, + kEditItem, + kDoubleClick, + kRecentDocsCount, + kRecentFoldersCount, + kRecentAppsCount, + kScrollUp, + kScrollDown, + kShowGroup, + kTraverseUp, + kNameChange +}; + +const rgb_color kAlmostWhite = {232, 232, 232, 255}; +const rgb_color kLightGray = {136, 136, 136, 255}; +const rgb_color kMediumGray = {96, 96, 96, 255}; + +const float kMinBtnWidth = 75.0f; +const float kMenuFieldPad = 30.0f; +const float kMenuFieldGap = 5.0f; +const float kCheckBoxPad = 20.0f; +const float kTextControlPad = 15.0f; + +const char *const kOpenStr = "Open"; +const char *const kRemoveStr = "Remove"; +const char *const kShowGroupStr = "Show Group"; +const char *const kSortByStr = "Sort By"; + +const char *const kNewGroupStr = "New Group"; +const char *const kRecentDocsStr = "Recent Documents"; +const char *const kRecentFoldersStr = "Recent Folders"; +const char *const kRecentAppsStr = "Recent Applications"; +const char *const kShowStr = "Show:"; + +const unsigned char kLargeNewGroupIcon [] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfa, 0xfa, 0x00, 0x00, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfa, 0xf8, 0xfa, 0xfa, 0x00, 0x00, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0xfa, 0xf8, 0xf8, 0xf8, 0xfa, 0xfa, 0x00, + 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfa, 0xfa, 0x00, 0x00, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xfa, + 0xfa, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfa, 0xf8, 0xfa, 0xfa, 0x00, 0x00, 0xf8, 0xf8, 0xf8, 0xf8, + 0xf8, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfa, 0xf8, 0xf8, 0xf8, 0xfa, 0xfa, 0x00, 0x00, 0xf8, 0xf8, + 0xf8, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0xfa, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xfa, 0xfa, 0x00, 0xf8, + 0xf8, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1b, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0x00, 0x0e, + 0x0f, 0x1c, 0x1c, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1b, 0x0f, 0x0f, 0x0f, 0xf8, 0xf8, 0xf8, 0xf8, 0x00, 0x3f, + 0x3f, 0x0e, 0x0f, 0x1c, 0x1c, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1b, 0x0f, 0x3f, 0x3f, 0x0e, 0x0f, 0xf8, 0xf8, 0x00, 0x00, + 0x3f, 0x3f, 0x3f, 0x0e, 0x0f, 0x1c, 0x1c, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1b, 0x0f, 0x3f, 0x3f, 0x3f, 0x3f, 0x0e, 0x0f, 0x1c, 0x1c, + 0x00, 0x00, 0x3f, 0x3f, 0x3f, 0x0e, 0x0f, 0x1a, 0x19, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1b, 0x0f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x0e, 0x0f, + 0x1c, 0x1c, 0x00, 0x00, 0x3f, 0x3f, 0x3f, 0x0e, 0x1a, 0x19, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1b, 0x0f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, + 0x0e, 0x0f, 0x1c, 0x1c, 0x00, 0x00, 0x3f, 0x3f, 0x17, 0x19, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1b, 0x0f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, + 0x3f, 0x3f, 0x0e, 0x0f, 0x1a, 0x19, 0x00, 0x00, 0x17, 0x19, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1b, 0x0f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, + 0x3f, 0x3f, 0x3f, 0x3f, 0x0e, 0x1a, 0x19, 0x00, 0x17, 0x19, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1b, 0x0f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x17, 0x19, 0x00, 0x17, 0x19, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1b, 0x0f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x17, 0x19, 0x00, 0x17, 0x19, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1b, 0x0f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x17, 0x19, 0x00, 0x17, 0x19, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1b, 0x18, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x17, 0x19, 0x00, 0x17, 0x19, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x1b, 0x1c, 0x17, 0x18, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x17, 0x19, 0x00, 0x17, 0x19, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x1b, 0x1c, 0x17, 0x18, 0x3f, 0x3f, 0x3f, 0x3f, + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x17, 0x19, 0x00, 0x17, 0x19, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x19, 0x1a, 0x17, 0x17, 0x3f, 0x3f, + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x17, 0x19, 0x00, 0x17, 0x19, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x19, 0x1a, 0x17, 0x17, + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x17, 0x19, 0x00, 0x17, 0x1a, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x19, 0x1a, + 0x17, 0x17, 0x3f, 0x3f, 0x3f, 0x17, 0x19, 0x00, 0x1a, 0x19, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, + 0x19, 0x1a, 0x17, 0x17, 0x3f, 0x17, 0x19, 0x00, 0x00, 0x19, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x00, 0x00, 0x19, 0x1a, 0x17, 0x17, 0x1a, 0x00, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x00, 0x00, 0x19, 0x1a, 0x19, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x19, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +const unsigned char kSmallNewGroupIcon [] = { + 0xff, 0xff, 0xff, 0xff, 0x0e, 0x0e, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x0e, 0xfa, 0xfa, 0x0e, 0x0e, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x0e, 0x0e, 0xf8, 0xf8, 0xfa, 0xfa, 0x0e, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x0e, 0xfa, 0xfa, 0x0e, 0xf8, 0xf8, 0x0e, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x0e, 0xf8, 0xf8, 0xfa, 0xfa, 0x0e, 0x0e, 0x0e, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x0e, 0x18, 0x18, 0xf8, 0xf8, 0x0e, 0x3f, 0x3f, 0x0e, 0x0e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x0e, 0x18, 0x3f, 0x18, 0x18, 0x0f, 0x0f, 0x3f, 0x3f, 0x0e, 0x12, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x0e, 0x18, 0x3f, 0x3f, 0x3f, 0x18, 0x18, 0x0f, 0x0f, 0x0e, 0x12, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x0e, 0x18, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x18, 0x18, 0x0e, 0x12, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x0e, 0x18, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x1c, 0x0e, 0x12, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x0e, 0x0e, 0x18, 0x1c, 0x3f, 0x3f, 0x3f, 0x3f, 0x1c, 0x0e, 0x12, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x0e, 0x0e, 0x17, 0x1c, 0x3f, 0x3f, 0x1c, 0x0e, 0x12, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0e, 0x0e, 0x17, 0x1c, 0x1c, 0x0e, 0x12, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0e, 0x0e, 0x17, 0x0e, 0x12, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0e, 0x0e, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; + + +void +CenterWindowOnScreen(BWindow *window) +{ + BRect screenFrame = BScreen(B_MAIN_SCREEN_ID).Frame(); + BPoint point; + point.x = screenFrame.Width() / 2 - window->Bounds().Width() / 2; + point.y = screenFrame.Height() / 2 - window->Bounds().Height() / 2; + + if (screenFrame.Contains(point)) + window->MoveTo(point); +} + + +float +FontHeight(const BFont *font, bool full) +{ + font_height finfo; + font->GetHeight(&finfo); + float height = finfo.ascent + finfo.descent; + + if (full) + height += finfo.leading; + + return height; +} + + +// #pragma mark - + + +TFavoritesConfigWindow::TFavoritesConfigWindow(BRect frame, const char *title, + bool modal, uint32 filePanelNodeFlavors, BMessenger parent, const entry_ref *startRef, + int32 maxApps, int32 maxDocs, int32 maxFolders) + : BWindow(frame, title, B_TITLED_WINDOW_LOOK, + modal ? B_MODAL_APP_WINDOW_FEEL : B_NORMAL_WINDOW_FEEL, + B_NOT_ZOOMABLE | B_NOT_RESIZABLE), + fFilePanelNodeFlavors(filePanelNodeFlavors), + fParent(parent), + fCurrentRef(*startRef), + fAddPanel(NULL) +{ + Lock(); + + // show the window offscreen + // so that the placement calculations actually work + MoveTo(-1024, -1024); + Show(); + AddParts(maxApps, maxDocs, maxFolders); + CenterWindowOnScreen(this); + Unlock(); + + AddShortcut('R', B_COMMAND_KEY, new BMessage(kRemove)); + AddShortcut('A', B_COMMAND_KEY, new BMessage(kAdd)); + AddShortcut('E', B_COMMAND_KEY, new BMessage(kEditItem)); + AddShortcut('O', B_COMMAND_KEY, new BMessage(kOpenItem)); + AddShortcut('N', B_COMMAND_KEY, new BMessage(kNewGroup)); + + AddShortcut(B_UP_ARROW, B_COMMAND_KEY, new BMessage(kTraverseUp)); + + BMessenger tracker(kTrackerSignature); + StartWatching(tracker, kFavoriteCountChanged); +} + + +TFavoritesConfigWindow::~TFavoritesConfigWindow() +{ + // node monitoring the be menu directory + stop_watching(this); + + if (fAddPanel && fAddPanel->IsShowing()) + // kill the filepanel if its still showing + fAddPanel->Hide(); + + BMessenger tracker(kTrackerSignature); + StopWatching(tracker, kFavoriteCountChanged); +} + + +void +TFavoritesConfigWindow::MessageReceived(BMessage *message) +{ + switch (message->what) { + // from menuthing selection + case kItemSelected: + UpdateButtons(); + break; + + // double click on item in menuthing + case kDoubleClick: + { + entry_ref ref; + if (message->FindRef("current", &ref) == B_OK) + OpenGroup(&ref); + } + break; + + // open button + case kOpenItem: + if (fMenuThing->Value() >= 0 && fMenuThing->Value() < fMenuThing->ItemCount()) + OpenGroup(fMenuThing->ItemAt(fMenuThing->Value())->EntryRef()); + break; + + case kEditItem: + { + int32 selection = fMenuThing->Value(); + if (selection < 0) + break; + const Model *item = fMenuThing->ItemAt(selection); + if (!item) + break; + + new NameItemPanel(this, item->Name()); + // shows itself, kills itself + } + break; + + case 'canc': + break; + + case kNameChange: + { + const char *name; + if (message->FindString("name", &name) == B_OK) { + int32 selection = fMenuThing->Value(); + if (selection < 0) + break; + const Model *item = fMenuThing->ItemAt(selection); + if (!item) + break; + + const entry_ref *ref = item->EntryRef(); + if (strcmp(ref->name, name) != 0) { + BEntry entry(ref); + if (entry.InitCheck() == B_OK && entry.Exists()) + entry.Rename(name); + } + } + } + break; + + // change of selection in directory menu + case kShowGroup: + { + entry_ref ref; + if (message->FindRef("current", &ref) == B_OK) + ShowGroup(&ref); + } + break; + + // alt-up arrow + case kTraverseUp: + { + BMenuItem *item = fGroupMenu->FindMarked(); + if (item) { + int32 index = fGroupMenu->IndexOf(item) - 1; + if (index < 0) + break; + + item = fGroupMenu->ItemAt(index); + if (item) { + BMessage *message = item->Message(); + if (message) { + entry_ref ref; + if (message->FindRef("current", &ref) == B_OK) { + ShowGroup(&ref); + } + } + } + } + } + break; + + // new group icon + case kNewGroup: + AddNewGroup(); + break; + + // Add button + case kAdd: + PromptForAdd(); + break; + + // from Add btn - FilePanel + case B_REFS_RECEIVED: + AddRefs(message); + // fall through + case B_CANCEL: + fAddPanel = NULL; + break; + + // remove btn + case kRemove: + fMenuThing->RemoveItem(fMenuThing->Value()); + break; + + // recents items + case kRecentFolders: + fRecentFoldersFld->SetEnabled(fRecentFoldersBtn->Value() != 0); + if (fRecentFoldersBtn->Value()) + UpdateFoldersCount(); + else + UpdateFoldersCount(0); + break; + + case kRecentDocs: + fRecentDocsFld->SetEnabled(fRecentDocsBtn->Value() != 0); + if (fRecentDocsBtn->Value()) + UpdateDocsCount(); + else + UpdateDocsCount(0); + break; + + case kRecentApps: + fRecentAppsFld->SetEnabled(fRecentAppsBtn->Value() != 0); + if (fRecentAppsBtn->Value()) + UpdateAppsCount(); + else + UpdateAppsCount(0); + break; + + case kRecentFoldersCount: + UpdateFoldersCount(); + break; + + case kRecentDocsCount: + UpdateDocsCount(); + break; + + case kRecentAppsCount: + UpdateAppsCount(); + break; + + case B_OBSERVER_NOTICE_CHANGE: + { + int32 observerWhat; + if (message->FindInt32("be:observe_change_what", &observerWhat) == B_OK) { + switch (observerWhat) { + case kFavoriteCountChanged: + { + int32 appCount = 10; + int32 documentCount = 10; + int32 folderCount = 10; + + if (message->FindInt32("RecentApplications", &appCount) == B_OK) { + BString appStr; + appStr << appCount; + fRecentAppsFld->SetText(appStr.String()); + UpdateAppsCount(appCount, false); + // false -> do not tell tracker + } + + if (message->FindInt32("RecentDocuments", &documentCount) == B_OK) { + BString docStr; + docStr << documentCount; + fRecentDocsFld->SetText(docStr.String()); + UpdateDocsCount(documentCount, false); + // false -> do not tell tracker + } + + if (message->FindInt32("RecentFolders", &folderCount) == B_OK) { + BString folderStr; + folderStr << folderCount; + fRecentFoldersFld->SetText(folderStr.String()); + UpdateFoldersCount(folderCount, false); + // false -> do not tell tracker + } + } + break; + } + } + } + break; + + default: + BWindow::MessageReceived(message); + break; + } +} + + +bool +TFavoritesConfigWindow::QuitRequested() +{ + // tell the app the config panel is closing + BMessage message(kConfigClose); + int32 count; + if (fRecentAppsFld) { + if (fRecentAppsFld->IsEnabled()) + count = atoi(fRecentAppsFld->Text()); + else + count = 0; + message.AddInt32("applications", count); + } + if (fRecentFoldersFld) { + if (fRecentFoldersFld->IsEnabled()) + count = atoi(fRecentFoldersFld->Text()); + else + count = 0; + message.AddInt32("folders", count); + } + if (fRecentDocsFld) { + if (fRecentDocsFld->IsEnabled()) + count = atoi(fRecentDocsFld->Text()); + else + count = 0; + message.AddInt32("documents", count); + } + + fParent.SendMessage(&message); + return true; +} + + +void +TFavoritesConfigWindow::AddParts(int32 maxApps, int32 maxDocs, int32 maxFolders) +{ + // build the interface + AddBeMenuPane(maxApps, maxDocs, maxFolders); + + // find the bg with the largest dimensions + ResizeTo(fBeMenuPaneBG->Frame().Width(), fBeMenuPaneBG->Frame().Height()); + + // enable node watching + // fill the pseudo menu + OpenGroup(&fCurrentRef); +} + + +void +TFavoritesConfigWindow::BuildCommon(BRect *frame, int32 count, const char *string, + uint32 btnWhat, uint32 fldWhat, BCheckBox **button, BTextControl **field, BBox *parent) +{ + frame->right = frame->left + be_plain_font->StringWidth(string) + + kCheckBoxPad; + + BCheckBox *newButton = new BCheckBox(*frame, "recents btn", string, + new BMessage(btnWhat), B_FOLLOW_BOTTOM | B_FOLLOW_LEFT, + B_WILL_DRAW | B_NAVIGABLE); + parent->AddChild(newButton); + newButton->SetValue(count > 0); + + float width = be_plain_font->StringWidth(kShowStr); + frame->right = frame->left + width + + (be_plain_font->StringWidth("0") * 4) + kTextControlPad; + BTextControl *newFld = new BTextControl(*frame, "recents fld", kShowStr, "", + new BMessage(fldWhat), B_FOLLOW_BOTTOM | B_FOLLOW_LEFT, + B_WILL_DRAW | B_NAVIGABLE); + parent->AddChild(newFld); + newFld->SetDivider(width + 5); + newFld->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_CENTER); + newFld->SetEnabled(count>0); + + char str[32]; + sprintf(str, "%ld", count); + newFld->SetText(str); + + BTextView *textView = newFld->TextView(); + textView->SetMaxBytes(2); + + for (uint32 index = 0; index < 256; index++) + textView->DisallowChar(index); + for (uint32 index = '0'; index <= '9'; index++) + textView->AllowChar(index); + textView->AllowChar(B_BACKSPACE); + + *button = newButton; + *field = newFld; +} + + +void +TFavoritesConfigWindow::AddBeMenuPane(int32 maxApps, int32 maxDocs, int32 maxFolders) +{ + fBeMenuPaneBG = new BBox(Bounds(), "bg", B_FOLLOW_NONE, + B_WILL_DRAW, B_PLAIN_BORDER); + AddChild(fBeMenuPaneBG); + + BRect frame; + float width = 0; + + // New Group + fNewGroupBtn = new TDraggableIconButton(BRect(10, 0, 41, 31), kNewGroupStr, + new BMessage(kNewGroup), B_FOLLOW_LEFT | B_FOLLOW_BOTTOM, + B_WILL_DRAW | B_NAVIGABLE); + fBeMenuPaneBG->AddChild(fNewGroupBtn); + + // Recent Documents + frame.left = 10; + if (maxDocs > -1) { + BuildCommon(&frame, maxDocs, kRecentDocsStr, + kRecentDocs, kRecentDocsCount, &fRecentDocsBtn, &fRecentDocsFld, + fBeMenuPaneBG); + } else { + fRecentDocsBtn = NULL; + fRecentDocsFld = NULL; + } + + // Recent Applications + if (maxApps > -1) { + BuildCommon(&frame, maxApps, kRecentAppsStr, + kRecentApps, kRecentAppsCount, &fRecentAppsBtn, &fRecentAppsFld, + fBeMenuPaneBG); + } else { + fRecentAppsBtn = NULL; + fRecentAppsFld = NULL; + } + + // Recent Folders + if (maxFolders > -1) { + BuildCommon(&frame, maxFolders, kRecentFoldersStr, + kRecentFolders, kRecentFoldersCount, &fRecentFoldersBtn, &fRecentFoldersFld, + fBeMenuPaneBG); + } else { + fRecentFoldersBtn = NULL; + fRecentFoldersFld = NULL; + } + + // will place this items left edge relative to the contents list + fGroupMenu = new BPopUpMenu("Show Group"); + width = be_plain_font->StringWidth("Some Long Name For a Group") + + kMenuFieldPad; + frame.Set(0, 10, width, 11); + fGroupBtn = new BMenuField(frame, "group btn", kShowGroupStr, fGroupMenu); + fBeMenuPaneBG->AddChild(fGroupBtn); + fGroupBtn->HidePopUpMarker(); + fGroupBtn->SetDivider(0); + +#ifdef IS_SORTABLE + // not sortable now, ever (?) + fSortMenu = new BPopUpMenu("Sort By"); + fSortMenu->AddItem(new BMenuItem("Sort One", NULL)); + fSortMenu->AddItem(new BMenuItem("Sort Two", NULL)); + + frame.OffsetBy(0, fGroupBtn->Frame().Height() + kMenuFieldGap); + fSortBtn = new BMenuField(frame, "sort btn", kSortByStr, fSortMenu); + fBeMenuPaneBG->AddChild(fSortBtn); + fSortBtn->SetDivider(be_plain_font->StringWidth(kSortByStr) + 5); +#endif + + // Contents List + // placement is relative to controls in place + // may or may not have recents items + // always will have New Group button +#ifdef IS_SORTABLE + frame.top = fSortBtn->Frame().bottom + 10; +#else + frame.top = fGroupBtn->Frame().bottom + 10; +#endif + frame.bottom = frame.top + 1; + if (maxApps > -1 && fRecentAppsBtn) + frame.left = fRecentAppsBtn->Frame().right + 10; + else if (maxDocs > -1 && fRecentDocsBtn) + frame.left = fRecentDocsBtn->Frame().right + 10; + else if (maxFolders > -1 && fRecentFoldersBtn) + frame.left = fRecentFoldersBtn->Frame().right + 10; + else + frame.left = fGroupBtn->Frame().right + 10; + + frame.right = frame.left + 1; + fMenuThing = new TContentsMenu(frame, new BMessage(kItemSelected), + new BMessage(kDoubleClick), 10, &fCurrentRef); + fBeMenuPaneBG->AddChild(fMenuThing); + + width = be_plain_font->StringWidth("Edit"B_UTF8_ELLIPSIS); + frame.Set(0, 0, (kMinBtnWidth >= width) ? kMinBtnWidth : width, 1); + fEditBtn = new BButton(frame, "edit", "Edit"B_UTF8_ELLIPSIS, new BMessage(kEditItem), + B_FOLLOW_NONE, B_WILL_DRAW | B_NAVIGABLE); + fBeMenuPaneBG->AddChild(fEditBtn); + + width = be_plain_font->StringWidth(kOpenStr); + frame.Set(0, 0, (kMinBtnWidth >= width) ? kMinBtnWidth : width, 1); + fOpenBtn = new BButton(frame, "open", kOpenStr, new BMessage(kOpenItem), + B_FOLLOW_NONE, B_WILL_DRAW | B_NAVIGABLE); + fBeMenuPaneBG->AddChild(fOpenBtn); + + width = be_plain_font->StringWidth("Add"B_UTF8_ELLIPSIS); + frame.Set(0, 0, (kMinBtnWidth >= width) ? kMinBtnWidth : width, 1); + fAddBtn = new BButton(frame, "add", "Add"B_UTF8_ELLIPSIS, new BMessage(kAdd), + B_FOLLOW_NONE, B_WILL_DRAW | B_NAVIGABLE); + fBeMenuPaneBG->AddChild(fAddBtn); + + width = be_plain_font->StringWidth(kRemoveStr); + frame.Set(0, 0, (kMinBtnWidth >= width) ? kMinBtnWidth : width, 1); + fRemoveBtn = new BButton(frame, "remove", kRemoveStr, new BMessage(kRemove), + B_FOLLOW_NONE, B_WILL_DRAW | B_NAVIGABLE); + fBeMenuPaneBG->AddChild(fRemoveBtn); + + // initially disable buttons + fRemoveBtn->SetEnabled(false); + fOpenBtn->SetEnabled(false); + fEditBtn->SetEnabled(false); + + // contents list will auto resize + // resizing of parent pane will cause + // contents list and buttons to flow + fOpenBtn->MoveTo(fMenuThing->Frame().right - fOpenBtn->Frame().Width(), + fMenuThing->Frame().bottom + 10); + fEditBtn->MoveTo(fOpenBtn->Frame().left - 10 - fEditBtn->Frame().Width(), + fOpenBtn->Frame().top); + + fRemoveBtn->MoveTo(fOpenBtn->Frame().left, fOpenBtn->Frame().bottom + 5); + fAddBtn->MoveTo(fEditBtn->Frame().left, fRemoveBtn->Frame().top); + + // resize the pane so that everything is visible + fBeMenuPaneBG->ResizeTo(fMenuThing->Frame().right + 10, + fMenuThing->Frame().bottom + 10 + fOpenBtn->Frame().Height() + 5 + + fRemoveBtn->Frame().Height() + 10); + + // place the remaining controls relative to the menu thing + float bottom; + float left = 10; + BRect menuthingframe = fMenuThing->Frame(); + if (maxFolders > -1 && fRecentFoldersFld && fRecentFoldersBtn) { + bottom = menuthingframe.bottom; + fRecentFoldersFld->MoveTo( + menuthingframe.left - fRecentFoldersFld->Frame().Width() - 10, + bottom - fRecentFoldersFld->Frame().Height()); + fRecentFoldersBtn->MoveTo(10, + fRecentFoldersFld->Frame().top - fRecentFoldersBtn->Frame().Height()); + + left = fRecentFoldersBtn->Frame().right + - (fRecentFoldersBtn->Frame().Width() / 2) - (fNewGroupBtn->Frame().Width() / 2); + } + + if (maxApps > -1 && fRecentAppsFld && fRecentAppsBtn) { + if (maxFolders > -1 && fRecentFoldersFld && fRecentFoldersBtn) + bottom = fRecentFoldersBtn->Frame().top - 20; + else { + bottom = menuthingframe.bottom; + left = fRecentAppsBtn->Frame().right + - (fRecentAppsBtn->Frame().Width() / 2) - (fNewGroupBtn->Frame().Width() / 2); + } + + fRecentAppsFld->MoveTo( + menuthingframe.left - fRecentAppsFld->Frame().Width() - 10, + bottom - fRecentAppsFld->Frame().Height()); + fRecentAppsBtn->MoveTo(10, + fRecentAppsFld->Frame().top - fRecentAppsBtn->Frame().Height()); + } + + if (maxDocs > -1 && fRecentDocsFld && fRecentDocsBtn) { + if (maxApps > -1 && fRecentAppsFld && fRecentAppsBtn) + bottom = fRecentAppsBtn->Frame().top - 20; + else if (maxFolders > -1 && fRecentFoldersFld && fRecentFoldersBtn) + bottom = fRecentFoldersBtn->Frame().top - 20; + else { + bottom = menuthingframe.bottom; + left = fRecentDocsBtn->Frame().right + - (fRecentDocsBtn->Frame().Width() / 2) - (fNewGroupBtn->Frame().Width() / 2); + } + + fRecentDocsFld->MoveTo( + menuthingframe.left - fRecentDocsFld->Frame().Width() - 10, + bottom - fRecentDocsFld->Frame().Height()); + fRecentDocsBtn->MoveTo(10, + fRecentDocsFld->Frame().top - fRecentDocsBtn->Frame().Height()); + } + + // place this button relative (centered) to + // any recents items and content list + fNewGroupBtn->MoveTo(left, menuthingframe.top); + +#ifdef IS_SORTABLE + fSortBtn->MoveTo(menuthingframe.left - fSortBtn->Divider(), + fSortBtn->Frame().top); +#endif + fGroupBtn->MoveTo(menuthingframe.left - fGroupBtn->Divider(), + fGroupBtn->Frame().top); +} + + +static void +GetNextGroupName(BDirectory *dir, char *directoryName) +{ + int32 index = 1; + for (;;) { + sprintf(directoryName, "Untitled Group %li", index); + if (!dir->Contains(directoryName)) + break; + else + index++; + } +} + + +void +TFavoritesConfigWindow::AddNewGroup(entry_ref *dirRef, entry_ref *newGroup) +{ + BDirectory dir(dirRef); + + char directoryName[B_FILE_NAME_LENGTH]; + GetNextGroupName(&dir, directoryName); + + BDirectory subdir; + dir.CreateDirectory(directoryName, &subdir); + + BEntry entry; + subdir.GetEntry(&entry); + entry.GetRef(newGroup); +} + + +void +TFavoritesConfigWindow::AddSymLink(const entry_ref *dirRef, const entry_ref *target) +{ + BDirectory dir(dirRef); + BEntry entry(target); + BPath path; + entry.GetPath(&path); + BSymLink symlink; + dir.CreateSymLink(target->name, path.Path(), &symlink); +} + + +void +TFavoritesConfigWindow::AddNewGroup() +{ + // from New Group/Folder button + // add a new group/folder to this directory + entry_ref newGroup; + AddNewGroup(&fCurrentRef, &newGroup); +} + + +void +TFavoritesConfigWindow::PromptForAdd() +{ + if (fAddPanel) + fAddPanel->Show(); + // does an activate on the window + else { + // determine a starting point for where apps are added from + char appPath[B_PATH_NAME_LENGTH]; + + // search for an apps directory + if (find_directory(B_APPS_DIRECTORY, 0, false, appPath, B_PATH_NAME_LENGTH) == B_OK) { + entry_ref ref; + + // get reference to application directory + get_ref_for_path(appPath, &ref); + + fAddPanel = new BFilePanel(B_OPEN_PANEL, new BMessenger(this, this), + &ref, fFilePanelNodeFlavors, true); + } else + fAddPanel = new BFilePanel(B_OPEN_PANEL, new BMessenger(this, this), + NULL, fFilePanelNodeFlavors, true); + + fAddPanel->SetButtonLabel(B_DEFAULT_BUTTON, "Add"); + fAddPanel->Show(); + } +} + + +void +TFavoritesConfigWindow::AddRefs(BMessage *message) +{ + // add any refs in the message to the current directory + int32 index = 0; + entry_ref ref; + while (message->FindRef("refs", index, &ref) == B_OK) { + AddSymLink(&fCurrentRef, &ref); + index++; + } +} + + +// open a new ref to show, tunnelling down + +void +TFavoritesConfigWindow::OpenGroup(const entry_ref *ref) +{ + if (!ref) + return; + + fCurrentRef = *ref; + fMenuThing->SetStartRef(&fCurrentRef); + fMenuThing->SetValue(0); + + BMessage *message = new BMessage(kShowGroup); + message->AddRef("current", &fCurrentRef); + + ModelMenuItem *item = new ModelMenuItem(new Model(&fCurrentRef), fCurrentRef.name, message); + fGroupMenu->AddItem(item); + item->SetMarked(true); + + UpdateButtons(); +} + + +// specify a new ref to show, tunnelling up + +void +TFavoritesConfigWindow::ShowGroup(const entry_ref *groupRef) +{ + if (!groupRef) + return; + + entry_ref lastRef = fCurrentRef; + + fCurrentRef = *groupRef; + fMenuThing->SetStartRef(&fCurrentRef); + fMenuThing->Select(&lastRef); + + // find the item that was selected + int32 count = fGroupMenu->CountItems() - 1; + for (int32 index = count ; index >= 0 ; index--) { + BMenuItem *item = fGroupMenu->ItemAt(index); + if (item) { + BMessage *message = item->Message(); + if (message) { + entry_ref ref; + if (message->FindRef("current", &ref) == B_OK) { + + // if we have a match + // delete all the items below this item + if (ref == fCurrentRef) { + for (int32 j = count ; j > index ; j--) + delete fGroupMenu->RemoveItem(j); + + item->SetMarked(true); + break; + } + } + } + } + } + + UpdateButtons(); +} + + +void +TFavoritesConfigWindow::UpdateButtons() +{ + // Open is enabled only if this item is an actual directory + // Edit is enabled for any selection + // Remove is selected only if there is a selection + // Add is always enabled + int32 selection = fMenuThing->Value(); + + if (selection >= 0 && selection < fMenuThing->ItemCount()) + fOpenBtn->SetEnabled(fMenuThing->ItemAt(selection)->IsDirectory()); + else + fOpenBtn->SetEnabled(false); + + fEditBtn->SetEnabled(selection >= 0); + fRemoveBtn->SetEnabled(selection >= 0); +} + + +void +TFavoritesConfigWindow::UpdateFoldersCount(int32 count, bool notifyTracker) +{ + if (count == -1) + count = atoi(fRecentFoldersFld->Text()); + + BMessage message(kUpdateFolderCount); + message.AddInt32("count", count); + fParent.SendMessage(&message); + + if (notifyTracker) { + BMessenger tracker(kTrackerSignature); + BMessage notificationMessage(kFavoriteCountChangedExternally); + notificationMessage.AddInt32("RecentFolders", count); + tracker.SendMessage(¬ificationMessage); + } +} + + +void +TFavoritesConfigWindow::UpdateDocsCount(int32 count, bool notifyTracker) +{ + if (count == -1) + count = atoi(fRecentDocsFld->Text()); + + BMessage message(kUpdateDocsCount); + message.AddInt32("count", count); + fParent.SendMessage(&message); + + if (notifyTracker) { + BMessenger tracker(kTrackerSignature); + BMessage notificationMessage(kFavoriteCountChangedExternally); + notificationMessage.AddInt32("RecentDocuments", count); + tracker.SendMessage(¬ificationMessage); + } +} + + +void +TFavoritesConfigWindow::UpdateAppsCount(int32 count, bool notifyTracker) +{ + if (count == -1) + count = atoi(fRecentAppsFld->Text()); + + BMessage message(kUpdateAppsCount); + message.AddInt32("count", count); + fParent.SendMessage(&message); + + if (notifyTracker) { + BMessenger tracker(kTrackerSignature); + BMessage notificationMessage(kFavoriteCountChangedExternally); + notificationMessage.AddInt32("RecentApplications", count); + tracker.SendMessage(¬ificationMessage); + } +} + + +// *************************************************************************************** + + +TDraggableIconButton::TDraggableIconButton(BRect frame, const char *label, + BMessage *message, uint32 resizeMask, uint32 flags) + : BControl(frame, "draggable icon", label, message, resizeMask, flags), + fIcon(NULL) +{ +} + + +TDraggableIconButton::~TDraggableIconButton() +{ +} + + +void +TDraggableIconButton::AttachedToWindow() +{ + BControl::AttachedToWindow(); + + if (Parent()) + SetViewColor(Parent()->ViewColor()); + else + SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + + fIcon = new BBitmap(BRect(0, 0, 31, 31), B_COLOR_8_BIT); + fIcon->SetBits(kLargeNewGroupIcon, fIcon->BitsLength(), 0, B_COLOR_8_BIT); + // calculate correct frame for icon and label + // sets icon rect and label rect for drawing + ResizeToPreferred(); +} + + +void +TDraggableIconButton::DetachedFromWindow() +{ + BControl::DetachedFromWindow(); + delete fIcon; + fIcon = NULL; +} + + +void +TDraggableIconButton::Draw(BRect) +{ + PushState(); + + SetHighColor(kBlack); + SetLowColor(ViewColor()); + + if (fIcon) { + SetDrawingMode(B_OP_OVER); + DrawBitmapAsync(fIcon, fIconRect); + } + + SetDrawingMode(B_OP_COPY); + MovePenTo(fLabelRect.LeftBottom()); + DrawString(Label()); + + if (IsEnabled() && IsFocus() && Window()->IsActive()) + SetHighColor(kBlack); + else + SetHighColor(ViewColor()); + StrokeRect(Bounds()); + + PopState(); +} + + +void +TDraggableIconButton::MouseDown(BPoint where) +{ + ulong buttons; + BPoint loc; + GetMouse(&loc, &buttons); + if (!buttons) + return; + + fInitialClickRect.Set(where.x, where.y, where.x, where.y); + fInitialClickRect.InsetBy(-4, -4); + SetTracking(true); + fDragging = false; + SetMouseEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY | B_LOCK_WINDOW_FOCUS); + InvertRect(fIconRect); +} + + +void +TDraggableIconButton::MouseUp(BPoint where) +{ + if (IsTracking()) { + InvertRect(fIconRect); + SetTracking(false); + fDragging = false; + if (Bounds().Contains(where) && fInitialClickRect.Contains(where)) + // tell parent to add a new group + Invoke(); + + } else + BControl::MouseUp(where); +} + + +void +TDraggableIconButton::MouseMoved(BPoint where, uint32 code, + const BMessage *message) +{ + if (IsTracking()) { + if (!fDragging && fIcon) { + DragMessage(new BMessage('icon'), new BBitmap(fIcon), + B_OP_BLEND, BPoint(15, 15)); + fDragging = true; + fInitialClickRect.Set(0, 0, 0, 0); + } + } else + BControl::MouseMoved(where, code, message); +} + + +void +TDraggableIconButton::GetPreferredSize(float *width, float *height) +{ + float stringWidth = be_plain_font->StringWidth(Label()); + float fontHeight = FontHeight(be_plain_font, true); + *width = stringWidth + 10; + *height = 32 + fontHeight + 5; + + fIconRect.Set((*width / 2) - 16, 2, (*width / 2) + 15, 33); + fLabelRect.Set((*width / 2) - (stringWidth / 2), + *height - fontHeight - 5, *width, *height - 5); +} + + +void +TDraggableIconButton::ResizeToPreferred() +{ + float width, height; + GetPreferredSize(&width, &height); + ResizeTo(width, height); +} + + +const float kLeftGutter = 15.0f; +const float kHorizontalGap = 4.0f; +const float kItemGap = 4.0f; +const float kVerticalBorderSize = 2.0f; +const float kHorizontalBorderSize = 2.0f; +const int32 kMaxItemCount = 12; +const float kScrollerHeight = 16.0f; + + +TContentsMenu::TContentsMenu(BRect frame, BMessage *singleClick, BMessage *doubleClick, + int32 visibleItemCount, const entry_ref *startRef) + : BControl(frame, "contents menu", "contents menu label", + singleClick, B_FOLLOW_NONE, B_WILL_DRAW | B_NAVIGABLE), + fDoubleClickMessage(doubleClick), + fVisibleItemCount(visibleItemCount), + fStartRef(*startRef), + fItemHeight(16), +#ifdef ITEM_EDIT + fEditingItem(false), + fEditingFld(NULL), +#endif + fFirstItem(0), + fContentsList(NULL), + fUpBtn(NULL), + fDownBtn(NULL) +{ +} + + +TContentsMenu::~TContentsMenu() +{ + delete fDoubleClickMessage; +} + + +void +TContentsMenu::AttachedToWindow() +{ + BControl::AttachedToWindow(); + + menu_info minfo; + get_menu_info(&minfo); + + // cache the menu font for drawing later on +#ifdef USE_MENU_FONT + // !! this should change when the menu font is used + fMenuFont = new BFont(); + fMenuFont->SetFamilyAndStyle(minfo.f_family, minfo.f_style); + fMenuFont->SetSize(minfo.font_size); +#else + fMenuFont = new BFont(be_plain_font); +#endif + // cache the item height, greater of font height and icon height + fFontHeight = FontHeight(fMenuFont, true); + fItemHeight = (fFontHeight>fItemHeight) ? fFontHeight : fItemHeight; + fItemHeight += 3; // add a gutter + + // mimicing a menu + SetViewColor(minfo.background_color); +#ifdef SNAKE + fHiliteColor = ui_color(B_MENU_SELECTION_BACKGROUND_COLOR); +#else + fHiliteColor = tint_color(minfo.background_color, B_DARKEN_2_TINT); +#endif + // add the buttons + // cache item frame, sub of frame - buttons + fContentsList = new BObjectList(kMaxItemCount); + + SetValue(-1); // no item selected + + ResizeToPreferred(); + + BRect frame(2, 2, Bounds().Width() - 2, 2 + kScrollerHeight); + fUpBtn = new TScrollerButton(frame, new BMessage(kScrollDown), true); + AddChild(fUpBtn); + fUpBtn->SetTarget(this, Window()); + + frame.bottom = Bounds().Height()-2; + frame.top = frame.bottom - kScrollerHeight; + fDownBtn = new TScrollerButton(frame, new BMessage(kScrollUp), false); + AddChild(fDownBtn); + fDownBtn->SetTarget(this, Window()); + + // cache the new group icon + fSmallGroupIcon = new BBitmap(BRect(0, 0, 15, 15), B_COLOR_8_BIT); + fSmallGroupIcon->SetBits(kSmallNewGroupIcon, fSmallGroupIcon->BitsLength(), + 0, B_COLOR_8_BIT); + + // cache the symlink icon + BMimeType symlink("application/x-vnd.Be-symlink"); + if (symlink.InitCheck() == B_OK) { + fSymlinkIcon = new BBitmap(BRect(0, 0, 15, 15), B_COLOR_8_BIT); + if (symlink.GetIcon(fSymlinkIcon, B_MINI_ICON) != B_OK) + fSymlinkIcon = NULL; + } else + fSymlinkIcon = NULL; + + // set up node watch etc + SetStartRef(&fStartRef); +} + + +void +TContentsMenu::DetachedFromWindow() +{ + EmptyMenu(); + delete fContentsList; + fContentsList = NULL; + delete fMenuFont; + fMenuFont = NULL; + delete fSmallGroupIcon; + fSmallGroupIcon = NULL; + delete fSymlinkIcon; + fSymlinkIcon = NULL; + BControl::DetachedFromWindow(); +} + + +const char *kUntitledItemStr = ""; + +void +TContentsMenu::Draw(BRect updateRect) +{ + PushState(); + + // draw frame + SetLowColor(ViewColor()); + + BRect frame(Bounds()); + // main border + +#ifdef SNAKE + SetHighColor(kBlack); + StrokeRoundRect(frame, 3.0, 3.0); +#else + // exterior border + SetHighColor(100, 100, 100); + StrokeRect(frame); + + // interior white + SetHighColor(233, 233, 233); + // top + StrokeLine(frame.LeftTop() + BPoint(1, 1), frame.RightTop() + BPoint(-1, 1)); + // left + StrokeLine(frame.LeftTop() + BPoint(1, 1), frame.LeftBottom() + BPoint(1, -1)); + + // interior gray + SetHighColor(141, 141, 141); + // right + StrokeLine(frame.RightTop() + BPoint(-1, -1), frame.RightBottom() + BPoint(-1, -1)); + // bottom + StrokeLine(frame.LeftBottom() + BPoint(1, -1), frame.RightBottom() + BPoint(-1, -1)); + +#endif + + SetDrawingMode(B_OP_OVER); + + BRect iconFrame, textFrame, itemFrame; + + int32 max = fContentsList->CountItems() - fFirstItem; + int32 count = count = (max > kMaxItemCount) ? kMaxItemCount : max; + count += fFirstItem; + + for (int32 index = fFirstItem ; index < count ; index++) { + Model *item = fContentsList->ItemAt(index); + if (!item) + continue; + + if (!ItemFrame(index - fFirstItem, &iconFrame, &textFrame, &itemFrame)) + continue; + + if (!itemFrame.Intersects(updateRect)) + continue; + + // see if an item is selected + if (Value() == index) { + SetHighColor(fHiliteColor); + SetLowColor(fHiliteColor); +#ifdef SNAKE + FillRoundRect(itemFrame, 3.0, 3.0); + + // main frame + SetHighColor(kBlack); + StrokeRoundRect(itemFrame, 3.0, 3.0); +#else + FillRect(itemFrame); +#endif + } else { + SetHighColor(ViewColor()); + SetLowColor(ViewColor()); + FillRect(itemFrame); + } + + Model resolvedItem; + if (item->IsSymLink()) { + // if this item is a symlink + // see if it points to anything + // if it doesn't draw the broken symlink icon + BEntry entry(item->EntryRef(), true); + resolvedItem.SetTo(&entry); + if (entry.Exists()) { + // draw the real item icon + IconCache::sIconCache->Draw(&resolvedItem, this, iconFrame.LeftTop(), + kNormalIcon, B_MINI_ICON); + } else if (fSymlinkIcon){ + + SetDrawingMode(B_OP_OVER); + DrawBitmapAsync(fSymlinkIcon, iconFrame); + // this item doesn't exist + // get the name that the symlink has + resolvedItem.SetTo(item->EntryRef()); + } else { + // shouldn't ever get here + TRESPASS(); + entry_ref ref; + ref.set_name(""); + resolvedItem.SetTo(&ref); + } + } else { + if (item->IsDirectory() && fSmallGroupIcon) { + SetDrawingMode(B_OP_OVER); + DrawBitmapAsync(fSmallGroupIcon, iconFrame); + } else + IconCache::sIconCache->Draw(item, this, iconFrame.LeftTop(), + kNormalIcon, B_MINI_ICON); + + resolvedItem.SetTo(item->EntryRef()); + } + + // get the name of the resolved ref + // if this item points to /boot + // will retrieve the user name instead + const char *name = NULL; + if (resolvedItem.IsVolume()) { + BVolume volume(resolvedItem.NodeRef()->device); + char volumeName[B_FILE_NAME_LENGTH]; + volume.GetName(volumeName); + + name = volumeName; + } else if (item->IsSymLink()) { + // use the items symlink name + name = item->EntryRef()->name; + } else if (resolvedItem.Name() && resolvedItem.Name()[0] != '\0') + // use the items actual name + name = resolvedItem.Name(); + else + name = kUntitledItemStr; + + // truncate to fit appropriately + BString truncatedString(name); + fMenuFont->TruncateString(&truncatedString, B_TRUNCATE_END, textFrame.Width()); + + SetHighColor(kBlack); + SetFont(fMenuFont); + MovePenTo(textFrame.LeftBottom()); + DrawString(truncatedString.String()); + } + + PopState(); +} + + +void +TContentsMenu::InvalidateItem(int32 index) +{ + BRect dummy, itemrect; + if (ItemFrame(index, &dummy, &dummy, &itemrect)) + Invalidate(itemrect); +} + + +void +TContentsMenu::InvalidateAbsoluteItem(int32 index) +{ + index -= fFirstItem; + if (index >= 0 && index < kMaxItemCount) + InvalidateItem(index); +} + + +void +TContentsMenu::KeyDown(const char *bytes, int32 numBytes) +{ + if (IsEnabled() && IsFocus() && Window()->IsActive()) { + switch (bytes[0]) { + case B_DOWN_ARROW: + case B_LEFT_ARROW: + { +#ifdef ITEM_EDIT + StopItemEdit(); +#endif + int32 selection = Value() + 1; + if (selection < fContentsList->CountItems()) { + SetValueNoUpdate(selection); + InvalidateAbsoluteItem(selection - 1); + InvalidateAbsoluteItem(selection); + Invoke(); + } + // + // if the selection is the last item + // scroll the list down + // bottom item will be the selection + // + if (Value() - fFirstItem == kMaxItemCount) + Scroll(true); + } + break; + case B_UP_ARROW: + case B_RIGHT_ARROW: + { +#ifdef ITEM_EDIT + StopItemEdit(); +#endif + int32 selection = Value()-1; + if (selection >= 0) { + SetValueNoUpdate(selection); + InvalidateAbsoluteItem(selection + 1); + InvalidateAbsoluteItem(selection); + Invoke(); + } + + if (Value() - fFirstItem < 0) + Scroll(false); + } + break; + case B_ENTER: + case B_SPACE: + OpenItem(Value()); + break; + default: + BControl::KeyDown(bytes, numBytes); + break; + } + } +} + + +void +TContentsMenu::MessageReceived(BMessage *message) +{ + if (message->WasDropped()) { + if (message->what == 'icon') { + BPoint where; + if (message->FindPoint("_drop_point_", &where) != B_OK) { + where.x = -1; + where.y = -1; + } + AddTempItem(where); + } else { + TFavoritesConfigWindow *window = dynamic_cast(Window()); + if (window) + window->AddRefs(message); + } + } + + switch (message->what) { + // node monitor of be menu directory + case B_NODE_MONITOR: + { + // stash the entry_ref to the selected item + // use it after FillMenu to reselect the item + const entry_ref *oldref = ItemAt(Value())->EntryRef(); + entry_ref ref; + if (!oldref) + ref.set_name(""); + else + ref = *oldref; + + EmptyMenu(); + FillMenu(&fStartRef); + Select(&ref); + // checks for invalid ref + Invalidate(); + + // ask the window to update the buttons + Window()->PostMessage(kItemSelected); + } + break; + + // down button + case kScrollUp: +#ifdef ITEM_EDIT + StopItemEdit(); +#endif + Scroll(true); + break; + + // up button + case kScrollDown: +#ifdef ITEM_EDIT + StopItemEdit(); +#endif + Scroll(false); + break; + + default: + BControl::MessageReceived(message); + break; + } +} + + +void +TContentsMenu::MouseDown(BPoint where) +{ +#ifdef ITEM_EDIT + StopItemEdit(); +#endif + + bigtime_t clicktime; + get_click_speed(&clicktime); + + bigtime_t diff = system_time() - fInitialClickTime; + if (diff < clicktime && fInitialClickRect.Contains(where)) { + OpenItem(Value()); + return; + } + + fInitialClickRect.Set(where.x, where.y, where.x, where.y); + fInitialClickRect.InsetBy(-2, -2); + fInitialClickTime = system_time(); + + StartTracking(where); + MakeFocus(true); + SetMouseEventMask(B_POINTER_EVENTS); +} + + +void +TContentsMenu::StartTracking(BPoint where) +{ + SelectItemAt(where); + SetTracking(true); +} + + +void +TContentsMenu::StopTracking() +{ + if (IsTracking()) + SetTracking(false); +} + + +void +TContentsMenu::MouseUp(BPoint where) +{ + StopTracking(); +#ifdef ITEM_EDIT + BeginItemEdit(where); +#endif + BControl::MouseUp(where); +} + + +#ifdef ITEM_EDIT +void +TContentsMenu::BeginItemEdit(BPoint where) +{ + bigtime_t clicktime; + get_click_speed(&clicktime); + + bigtime_t diff = system_time() - fInitialClickTime; + + if (where == fInitialClickLoc && (diff > clicktime)) { + BRect iconFrame, textFrame, itemFrame; + + // !! get textFrame instead + // set viewcolor to menucolor + fItemIndex = ItemAt(where, &iconFrame, &textFrame, &itemFrame) + fFirstItem; + if (fItemIndex >= 0) { + // find the item + // add a textview + fEditingItem = true; + + delete fEditingFld; + + BRect trect(textFrame); + trect.OffsetTo(0, 0); + fEditingFld = new BTextView(textFrame, "edit", trect, B_FOLLOW_NONE); + AddChild(fEditingFld); + fEditingFld->SetViewColor(fHiliteColor); + + fEditingFld->SetText(ItemAt(fItemIndex)->EntryRef()->name); + fEditingFld->SelectAll(); + } + } else + fEditingItem = false; +} +#endif + + +#ifdef ITEM_EDIT +void +TContentsMenu::StopItemEdit() +{ + if (fEditingItem && fEditingFld) { + if (fItemIndex > -1) { + const char *name = fEditingFld->Text(); + const entry_ref *ref = ItemAt(fItemIndex)->EntryRef(); + if (strcmp(ref->name, name) != 0) { + BEntry entry(ref); + if (entry.InitCheck() == B_OK && entry.Exists()) + entry.Rename(name); + } + } + + // dispose the textview + if (fEditingFld) { + fEditingFld->RemoveSelf(); + delete fEditingFld; + fEditingFld = NULL; + fEditingItem = false; + fItemIndex = -1; + } + } +} +#endif + + +void +TContentsMenu::MouseMoved(BPoint where, uint32 code, const BMessage *message) +{ + switch (code) { + case B_ENTERED_VIEW: + + // if there is a message incoming + // then track with it for attempt at + // placement drop + // !! disabled for now +#if 0 + if (message) { + SelectItemAt(where); + SetTracking(true); + } +#endif + break; + + case B_INSIDE_VIEW: + if (IsTracking()) + SelectItemAt(where); + break; + + case B_EXITED_VIEW: + break; + + } + BControl::MouseMoved(where, code, message); +} + + +void +TContentsMenu::GetPreferredSize(float *width, float *height) +{ + // width should accomodate border, left gap, icon, separation, text, border + // height should acccomodate 14 itemheights + borders + + *width = kLeftGutter + B_MINI_ICON + kItemGap + + (fMenuFont->StringWidth("O") * 15) + + (2 * kVerticalBorderSize); + + *height = (2 * kScrollerHeight) // buttons + + (kMaxItemCount * fItemHeight) // items + + (2 * kHorizontalBorderSize) + 1; // border +} + + +void +TContentsMenu::ResizeToPreferred() +{ + float width, height; + GetPreferredSize(&width, &height); + ResizeTo(width, height); +} + + +void +TContentsMenu::SetStartRef(const entry_ref *ref) +{ + node_ref nref; + BEntry entry(&fStartRef); + if (entry.InitCheck() == B_OK) { + entry.GetNodeRef(&nref); + watch_node(&nref, B_STOP_WATCHING, this, Window()); + } + + fStartRef = *ref; + entry.SetTo(&fStartRef); + entry.GetNodeRef(&nref); + watch_node(&nref, B_WATCH_DIRECTORY, this, Window()); + + EmptyMenu(); + FillMenu(&fStartRef); + Invalidate(); +} + + +void +TContentsMenu::UpdateScrollers() +{ + int32 count = fContentsList->CountItems(); + if (count <= kMaxItemCount) { + fUpBtn->SetEnabled(false); + fDownBtn->SetEnabled(false); + } else { + fUpBtn->SetEnabled(fFirstItem != 0); + fDownBtn->SetEnabled(count-fFirstItem > kMaxItemCount); + } +} + + +void +TContentsMenu::Scroll(bool direction) +{ + int32 count = fContentsList->CountItems(); + if (count <= kMaxItemCount) + return; + + bool needtoupdate = false; + if (direction && fDownBtn->IsEnabled()) { // down + if ((count - fFirstItem) > kMaxItemCount) { + fFirstItem++; + needtoupdate = true; + } + } else if (!direction && fUpBtn->IsEnabled()) { // up + if (fFirstItem >= 1) { + fFirstItem--; + needtoupdate = true; + } + } + + if (needtoupdate) { + UpdateScrollers(); + BRect dummy, highrect; + ItemFrame(0, &dummy, &dummy, &highrect); + highrect.bottom = highrect.bottom + fItemHeight * (kMaxItemCount - 2); + BRect lowrect = highrect.OffsetByCopy(0, fItemHeight); + if (direction) { + CopyBits(lowrect, highrect); + InvalidateItem(kMaxItemCount - 1); + } else { + CopyBits(highrect, lowrect); + InvalidateItem(0); + } + } +} + + +static int +CompareOne(const Model *model1, const Model *model2) +{ + return strcasecmp(model1->Name(), model2->Name()); +} + + +void +TContentsMenu::FillMenu(const entry_ref *ref) +{ + if (!fContentsList) + fContentsList = new BObjectList(kMaxItemCount); + + BEntry entry(ref); + if (entry.InitCheck() == B_OK && entry.Exists()) { + BDirectory dir(ref); + BEntry nextEntry; + while (dir.GetNextEntry(&nextEntry) == B_OK) { + + // create a model for the actual item in the be folder + Model *model = new Model(&nextEntry); + fContentsList->AddItem(model); + } + + fContentsList->SortItems(CompareOne); + } + + int32 count = fContentsList->CountItems(); + if (count <= kMaxItemCount) + fFirstItem = 0; + else if (count - fFirstItem < kMaxItemCount) + fFirstItem = count - kMaxItemCount; + + UpdateScrollers(); +} + + +void +TContentsMenu::EmptyMenu() +{ + if (!fContentsList) + return; + + int32 count = fContentsList->CountItems()-1; + for (int32 index = count ; index >= 0 ; index--) { + Model *item = fContentsList->ItemAt(index); + if (item) { + fContentsList->RemoveItem(item); + delete item; + } + } +} + + +/** returns frames for visible items */ + +bool +TContentsMenu::ItemFrame(int32 index, BRect *iconFrame, BRect *textFrame, + BRect *itemFrame) const +{ + if (index >= kMaxItemCount || index < 0) + return false; + + float halfheight = fItemHeight / 2; + float halficon = B_MINI_ICON / 2; + float halffont = fFontHeight / 2; + BPoint loc; + loc.x = kLeftGutter; + loc.y = fUpBtn->Frame().bottom + 1 + halfheight; + + loc.y += index * fItemHeight; + + iconFrame->Set(loc.x, loc.y - halficon, loc.x + B_MINI_ICON, loc.y + halficon); + + textFrame->Set(loc.x + B_MINI_ICON + kItemGap, loc.y - halffont, + Bounds().right - 2, loc.y + halffont); + + itemFrame->Set(2, loc.y - halfheight, Bounds().Width() - 2, loc.y + halfheight - 1); + + return true; +} + + +/** returns index of visible item at location */ + +int32 +TContentsMenu::ItemAt(BPoint where, BRect *iconFrame, BRect *textFrame, + BRect *itemFrame) +{ + int32 count = fContentsList->CountItems(); + count = (count < kMaxItemCount) ? count : kMaxItemCount; + for (int32 index = 0 ; index < count ; index++) + if (ItemFrame(index, iconFrame, textFrame, itemFrame) + && itemFrame->Contains(where)) + return index; + + return -1; +} + + +/** returns entry_ref for item at absolute index */ + +const Model* +TContentsMenu::ItemAt(int32 index) const +{ + return fContentsList->ItemAt(index); +} + + +void +TContentsMenu::SelectItemAt(BPoint where) +{ + BRect iconFrame, textFrame, itemFrame; + + int32 previousvalue = Value(); + // get visible item + int32 index = ItemAt(where, &iconFrame, &textFrame, &itemFrame); + if (index <= -1) { + SetValueNoUpdate(-1); + Invoke(); + } else { + // get offset to actual item selected + index += fFirstItem; + if (index != Value()) { + SetValueNoUpdate(index); + Invoke(); + } + } + int32 newvalue = Value(); + if (previousvalue != newvalue) { + InvalidateAbsoluteItem(previousvalue); + InvalidateAbsoluteItem(newvalue); + } +} + + +void +TContentsMenu::Select(const entry_ref *ref) +{ + int32 select = -1; + if (ref && ref->name && ref->name[0] != '\0') { + int32 count = fContentsList->CountItems(); + for (int32 index = 0 ; index < count ; index++) { + Model *item = fContentsList->ItemAt(index); + if (item) { + if (*ref == *(item->EntryRef())) { + select = index; + break; + } + } + } + } + SetValue(select); + Invoke(); +} + + +void +TContentsMenu::OpenItem(int32 index) +{ + if (Value() >= 0 && index >= 0 && index < fContentsList->CountItems()) { + Model *item = fContentsList->ItemAt(index); + // see if we have an item + // only actual directories can be traversed + // and only if their hierarchy lives in the Be folder + + if (item && item->IsDirectory()) { + // if we have a double click message + // pass the new directory ref to the window + if (fDoubleClickMessage) { + if (fDoubleClickMessage->HasRef("current")) + fDoubleClickMessage->ReplaceRef("current", item->EntryRef()); + else + fDoubleClickMessage->AddRef("current", item->EntryRef()); + + Invoke(fDoubleClickMessage); + } + } + } +} + + +int32 +TContentsMenu::ItemCount() const +{ + return fContentsList->CountItems(); +} + + +static void +RemoveEntries(const entry_ref *ref) +{ + // should this delete on DB created items? + + BEntry entry(ref); + if (entry.InitCheck() == B_OK && entry.Exists()) { + // if its a directory + // check for contents and delete as necessary + if (entry.IsDirectory()) { + BDirectory dir(&entry); + BEntry nextEntry; + while (dir.GetNextEntry(&nextEntry) == B_OK) { + if (nextEntry.IsDirectory()) { + entry_ref nextref; + nextEntry.GetRef(&nextref); + RemoveEntries(&nextref); + } else + nextEntry.Remove(); + } + } + entry.Remove(); + } +} + + +void +TContentsMenu::RemoveItem(int32 index) +{ + int32 count = ItemCount() - 1; + + // index out of bounds + if (index < 0 || index > count) + return; + + RemoveEntries(ItemAt(index)->EntryRef()); + + // index was last item + if (index == count) { + SetValue(index - 1); + Invoke(); + } +} + + +void +TContentsMenu::AddTempItem(BPoint) +{ + // !! need to make a fake item for editing + // defaults, now, to simply adding a folder + TFavoritesConfigWindow *window = dynamic_cast(Window()); + if (window) + window->AddNewGroup(); +} + + +// *************************************************************************************** +// #pragma mark - + + +TScrollerButton::TScrollerButton(BRect frame, BMessage *message, bool direction) + : BControl(frame, "scroller", "scroller label", message, + B_FOLLOW_NONE, B_WILL_DRAW), + fDirection(direction), + fTicker(NULL) +{ +} + + +void +TScrollerButton::AttachedToWindow() +{ + BControl::AttachedToWindow(); + + menu_info minfo; + get_menu_info(&minfo); + fSelectedColor = tint_color(minfo.background_color, B_DARKEN_2_TINT); + + SetViewColor(minfo.background_color); + + fHiliteFrame = Bounds(); + if (fDirection) + fHiliteFrame.bottom -= 6; + else + fHiliteFrame.top += 4; +} + + +void +TScrollerButton::DetachedFromWindow() +{ + delete fTicker; + BControl::DetachedFromWindow(); +} + + +void +TScrollerButton::Draw(BRect) +{ + + PushState(); + + if (Value() == 0 || !IsEnabled()) { + SetLowColor(ViewColor()); + SetHighColor(ViewColor()); + } else { + SetLowColor(fSelectedColor); + SetHighColor(fSelectedColor); + } + + FillRect(fHiliteFrame); + + // add the triangle + if (IsEnabled()) + SetHighColor(kMediumGray); + else + SetHighColor(tint_color(kMediumGray, B_LIGHTEN_1_TINT)); + + float width = Bounds().Width(); + int32 linecount = Bounds().IntegerHeight() - 9; + + BPoint start(width / 2, fDirection ? 2 : Bounds().Height()-2); + BPoint finish(start); + + for (int32 index = 0 ; index < linecount ; index++) { + StrokeLine(start, finish); + --start.x; + ++finish.x; + if (fDirection) { + ++start.y; + ++finish.y; + } else { + --start.y; + --finish.y; + } + } + + // add the top/bottom delimiter + SetHighColor(kLightGray); + float y = fDirection ? start.y + 2 : start.y - 3; + StrokeLine(BPoint(0, y), BPoint(width, y)); + SetHighColor(kAlmostWhite); + y = fDirection ? start.y + 3 : start.y - 2; + StrokeLine(BPoint(0, y), BPoint(width, y)); + + PopState(); +} + + +void +TScrollerButton::MouseDown(BPoint where) +{ + if (IsEnabled() && fHiliteFrame.Contains(where)) { + SetValue(1); + Invoke(); + SetTracking(true); + SetMouseEventMask(B_POINTER_EVENTS); + fTicker = new BMessageRunner(BMessenger(Target()), Message(), 120000); + } +} + + +void +TScrollerButton::MouseUp(BPoint) +{ + SetValue(0); + delete fTicker; + fTicker = NULL; + if (IsTracking()) + SetTracking(false); +} + + +void +TScrollerButton::MouseMoved(BPoint where, uint32 code, const BMessage *message) +{ + switch (code) { + case B_EXITED_VIEW: + delete fTicker; + fTicker = NULL; + SetValue(0); + break; + + case B_ENTERED_VIEW: + if (IsEnabled() && IsTracking()) { + SetValue(1); + Invoke(); + fTicker = new BMessageRunner(BMessenger(Target()), Message(), 120000); + } + break; + + default: + break; + } + BControl::MouseMoved(where, code, message); +} + + +// *************************************************************************************** + +const char *kNewItemNameLabel = "New item name:"; + +const int32 kPanelWidth = 260; +const int32 kPanelHeight = 77; + +NameItemPanel::NameItemPanel(BWindow *parent, const char *initialtext) + : BWindow(BRect(0, 0, kPanelWidth, kPanelHeight), "", B_MODAL_WINDOW, + B_NOT_RESIZABLE | B_NOT_ZOOMABLE | B_NOT_CLOSABLE), + fParent(parent) +{ + MoveTo(-1024, -1024); + Show(); + Lock(); + AddParts(initialtext); + ResizeTo(Bounds().Width(), fCancelBtn->Frame().bottom + 10); + Unlock(); + CenterWindowOnScreen(this); +} + + +NameItemPanel::~NameItemPanel() +{ +} + + +void +NameItemPanel::MessageReceived(BMessage *message) +{ + switch (message->what){ + case 'done': + { + const char *text = fNameFld->Text(); + if (!text || text[0] == '\0'){ + if ((new BAlert("", "The new name is empty, please enter a name", + "Cancel", "OK", NULL, B_WIDTH_AS_USUAL))->Go() == 0) + return; + } + BMessage nameChangeMessage(kNameChange); + nameChangeMessage.AddString("name", text); + fParent->PostMessage(&nameChangeMessage); + PostMessage(B_QUIT_REQUESTED); + } + break; + + case 'canc': + fParent->PostMessage('canc'); + PostMessage(B_QUIT_REQUESTED); + break; + + default: + BWindow::MessageReceived(message); + break; + } +} + + +void +NameItemPanel::AddParts(const char *initialtext) +{ + fBG = new BBox(Bounds(), "bg", B_FOLLOW_ALL, B_WILL_DRAW, B_NO_BORDER); + AddChild(fBG); + + BRect rect(10, 10, Bounds().Width()-10, 11); + fNameFld = new BTextControl(rect, "", kNewItemNameLabel, "", NULL); + fBG->AddChild(fNameFld); + fNameFld->SetDivider(be_plain_font->StringWidth(kNewItemNameLabel) + 10); + fNameFld->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT); + if (initialtext && strlen(initialtext) > 0) + fNameFld->SetText(initialtext); + fNameFld->MakeFocus(true); + + BTextView *textView = fNameFld->TextView(); + if (textView) + textView->SetMaxBytes(32); + + rect.right = Bounds().Width() - 10; + rect.left = rect.right - 75; + rect.top = fNameFld->Frame().bottom + 10; + rect.bottom = rect.top + 1; + fDoneBtn = new BButton(rect, "", "Change", new BMessage('done'), + B_FOLLOW_TOP | B_FOLLOW_RIGHT); + + rect.right = rect.left - 10; + rect.left = rect.right - 75; + fCancelBtn = new BButton(rect, "", "Cancel", new BMessage('canc'), + B_FOLLOW_TOP | B_FOLLOW_RIGHT); + fBG->AddChild(fCancelBtn); + + fBG->AddChild(fDoneBtn); + SetDefaultButton(fDoneBtn); +} + diff --git a/src/kits/tracker/FavoritesMenu.cpp b/src/kits/tracker/FavoritesMenu.cpp new file mode 100644 index 0000000000..8a0f4fa60a --- /dev/null +++ b/src/kits/tracker/FavoritesMenu.cpp @@ -0,0 +1,416 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "EntryIterator.h" +#include "FavoritesMenu.h" +#include "IconMenuItem.h" +#include "NavMenu.h" +#include "PoseView.h" +#include "QueryPoseView.h" +#include "Tracker.h" +#include "Utilities.h" + + +FavoritesMenu::FavoritesMenu(const char *title, BMessage *openFolderMessage, + BMessage *openFileMessage, const BMessenger &target, + bool isSavePanel) + : BSlowMenu(title), + fOpenFolderMessage(openFolderMessage), + fOpenFileMessage(openFileMessage), + fTarget(target), + fContainer(NULL), + fInitialItemCount(0), + fIsSavePanel(isSavePanel) +{ +} + + +FavoritesMenu::~FavoritesMenu() +{ + delete fOpenFolderMessage; + delete fOpenFileMessage; + delete fContainer; +} + + +bool +FavoritesMenu::StartBuildingItemList() +{ + // initialize the menu building state + + if (!fInitialItemCount) + fInitialItemCount = CountItems(); + else { + // strip the old items so we can add new fresh ones + int32 count = CountItems() - fInitialItemCount; + // keep the items that were added by the FavoritesMenu creator + while (count--) + delete RemoveItem(fInitialItemCount); + } + + fUniqueRefCheck.clear(); + fState = kStart; + return true; +} + + +bool +FavoritesMenu::AddNextItem() +{ + // run the next chunk of code for a given item adding state + + if (fState == kStart) { + fState = kAddingFavorites; + fSectionItemCount = 0; + fAddedSeparatorForSection = false; + // set up adding the GoTo menu items + + try { + BPath path; + ThrowOnError( find_directory (B_USER_SETTINGS_DIRECTORY, &path, true) ); + path.Append(kGoDirectory); + mkdir(path.Path(), 0777); + + BEntry entry(path.Path()); + Model startModel(&entry, true); + ThrowOnInitCheckError(&startModel); + + if (!startModel.IsContainer()) + throw B_ERROR; + + if (startModel.IsQuery()) + fContainer = new QueryEntryListCollection(&startModel); + else + fContainer = new DirectoryEntryList(*dynamic_cast + (startModel.Node())); + + ThrowOnInitCheckError(fContainer); + ThrowOnError( fContainer->Rewind() ); + + } catch (...) { + delete fContainer; + fContainer = NULL; + } + } + + + if (fState == kAddingFavorites) { + entry_ref ref; + // limit nav menus to 20 items only + if (fContainer + && fSectionItemCount < 20 + && fContainer->GetNextRef(&ref) == B_OK) { + Model model(&ref, true); + if (model.InitCheck() != B_OK) + return true; + + BMenuItem *item = BNavMenu::NewModelItem(&model, + model.IsDirectory() ? fOpenFolderMessage : fOpenFileMessage, + fTarget); + + item->SetLabel(ref.name); // this is the name of the link in the Go dir + + if (!fAddedSeparatorForSection) { + fAddedSeparatorForSection = true; + AddItem(new TitledSeparatorItem("Favorite Folders")); + } + fUniqueRefCheck.push_back(*model.EntryRef()); + AddItem(item); + fSectionItemCount++; + return true; + } + + // done with favorites, set up for adding recent files + fState = kAddingFiles; + + fAddedSeparatorForSection = false; + + app_info info; + be_app->GetAppInfo(&info); + fItems.MakeEmpty(); + + int32 apps, docs, folders; + TrackerSettings().RecentCounts(&apps, &docs, &folders); + + BRoster().GetRecentDocuments(&fItems, docs, NULL, info.signature); + fIndex = 0; + fSectionItemCount = 0; + } + + if (fState == kAddingFiles) { + // if this is a Save panel, not an Open panel + // then don't add the recent documents + if (!fIsSavePanel) { + for (;;) { + entry_ref ref; + if (fItems.FindRef("refs", fIndex++, &ref) != B_OK) + break; + Model model(&ref, true); + if (model.InitCheck() != B_OK) + return true; + + BMenuItem *item = BNavMenu::NewModelItem(&model, fOpenFileMessage, fTarget); + if (item) { + if (!fAddedSeparatorForSection) { + fAddedSeparatorForSection = true; + AddItem(new TitledSeparatorItem("Recent Documents")); + } + AddItem(item); + fSectionItemCount++; + return true; + } + } + } + + // done with recent files, set up for adding recent folders + fState = kAddingFolders; + + fAddedSeparatorForSection = false; + + app_info info; + be_app->GetAppInfo(&info); + fItems.MakeEmpty(); + + int32 apps, docs, folders; + TrackerSettings().RecentCounts(&apps, &docs, &folders); + + BRoster().GetRecentFolders(&fItems, folders, info.signature); + fIndex = 0; + } + + if (fState == kAddingFolders) { + for (;;) { + entry_ref ref; + if (fItems.FindRef("refs", fIndex++, &ref) != B_OK) + break; + + // don't add folders that are already in the GoTo section + if (find_if(fUniqueRefCheck.begin(), fUniqueRefCheck.end(), + bind2nd(std::equal_to(), ref)) != fUniqueRefCheck.end()) + continue; + + Model model(&ref, true); + if (model.InitCheck() != B_OK) + return true; + + BMenuItem *item = BNavMenu::NewModelItem(&model, fOpenFolderMessage, + fTarget, true); + if (item) { + if (!fAddedSeparatorForSection) { + fAddedSeparatorForSection = true; + AddItem(new TitledSeparatorItem("Recent Folders")); + } + AddItem(item); + item->SetEnabled(true); + // BNavMenu::NewModelItem returns a disabled item here - + // need to fix this in BNavMenu::NewModelItem + return true; + } + } + } + return false; +} + + +void +FavoritesMenu::DoneBuildingItemList() +{ + SetTargetForItems(fTarget); +} + + +void +FavoritesMenu::ClearMenuBuildingState() +{ + delete fContainer; + fContainer = NULL; + fState = kDone; + + // force the menu to get rebuilt each time + fMenuBuilt = false; +} + + +// #pragma mark - + + +RecentsMenu::RecentsMenu(const char *name,int32 which,uint32 what,BHandler *target) + : BNavMenu(name, what, target), + fWhich(which), + fRecentsCount(0), + fItemIndex(0) +{ + int32 applications; + int32 documents; + int32 folders; + TrackerSettings().RecentCounts(&applications,&documents,&folders); + + if (fWhich == 0) + fRecentsCount = documents; + else if (fWhich == 1) + fRecentsCount = applications; + else if (fWhich == 2) + fRecentsCount = folders; +} + + +void +RecentsMenu::DetachedFromWindow() +{ + // + // BNavMenu::DetachedFromWindow sets the TypesList to NULL + // + BMenu::DetachedFromWindow(); +} + + +bool +RecentsMenu::StartBuildingItemList() +{ + int32 count = CountItems()-1; + for (int32 index = count; index >= 0; index--) { + BMenuItem *item = ItemAt(index); + ASSERT(item); + + RemoveItem(index); + delete item; + } + // + // !! note: don't call inherited from here + // the navref is not set for this menu + // but it still needs to be a draggable navmenu + // simply return true so that AddNextItem is called + // + // return BNavMenu::StartBuildingItemList(); + return true; +} + + +bool +RecentsMenu::AddNextItem() +{ + if (fRecentsCount > 0 && AddRecents(fRecentsCount)) + return true; + + fItemIndex = 0; + return false; +} + + +bool +RecentsMenu::AddRecents(int32 count) +{ + if (fItemIndex == 0) { + fRecentList.MakeEmpty(); + BRoster roster; + + switch(fWhich) { + case 0: + roster.GetRecentDocuments(&fRecentList, count); + break; + case 1: + roster.GetRecentApps(&fRecentList, count); + break; + case 2: + roster.GetRecentFolders(&fRecentList, count); + break; + default: + return false; + break; + } + } + for (;;) { + entry_ref ref; + if (fRecentList.FindRef("refs", fItemIndex++, &ref) != B_OK) + break; + + if (ref.name && strlen(ref.name) > 0) { + Model model(&ref, true); + ModelMenuItem *item = BNavMenu::NewModelItem(&model, + new BMessage(fMessage.what), + Target(), false, NULL, TypesList()); + + if (item) { + AddItem(item); + + // return true so that we know to reenter this list + return true; + } + return true; + } + } + + // + // return false if we are done with this list + // + return false; +} + + +void +RecentsMenu::DoneBuildingItemList() +{ + // + // !! note: don't call inherited here + // the object list is not built + // and this list does not need to be sorted + // BNavMenu::DoneBuildingItemList(); + // + + if (CountItems() <= 0) { + BMenuItem *item = new BMenuItem("", 0); + item->SetEnabled(false); + AddItem(item); + } else + SetTargetForItems(Target()); +} + + +void +RecentsMenu::ClearMenuBuildingState() +{ + fMenuBuilt = false; + BNavMenu::ClearMenuBuildingState(); +} + diff --git a/src/kits/tracker/FavoritesMenu.h b/src/kits/tracker/FavoritesMenu.h new file mode 100644 index 0000000000..213a92581e --- /dev/null +++ b/src/kits/tracker/FavoritesMenu.h @@ -0,0 +1,129 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef __FAVORITES_MENU__ +#define __FAVORITES_MENU__ + +#include + +#include "NavMenu.h" +#include "ObjectList.h" + +namespace BPrivate { + +class EntryListBase; + +#define kGoDirectory "Tracker/Go" + +class FavoritesMenu : public BSlowMenu { + // FavoritesMenu is used in the FilePanel - + // displays recent files, recent folders and favorites items + public: + FavoritesMenu(const char *title, BMessage *openFolderMessage, + BMessage *openFileMessage, const BMessenger &, + bool isSavePanel); + virtual ~FavoritesMenu(); + + private: + // override the necessary SlowMenu hooks + virtual bool StartBuildingItemList(); + virtual bool AddNextItem(); + virtual void DoneBuildingItemList(); + virtual void ClearMenuBuildingState(); + + BMessage *fOpenFolderMessage; + BMessage *fOpenFileMessage; + BMessenger fTarget; + + enum State { + kStart, + kAddingFavorites, + kAddingFiles, + kAddingFolders, + kDone + }; + + State fState; + + int32 fIndex; + int32 fSectionItemCount; + bool fAddedSeparatorForSection; + // keeps track wether a separator will be needed before the + // next inserted item + BMessage fItems; + + EntryListBase *fContainer; + BObjectList *fItemList; + int32 fInitialItemCount; + std::vector fUniqueRefCheck; + bool fIsSavePanel; + + typedef BSlowMenu _inherited; +}; + + +enum recent_type { + kRecentDocuments = 0, + kRecentApplications = 1, + kRecentFolders = 2 +}; + +class RecentsMenu : public BNavMenu { + public: + RecentsMenu(const char *name,int32 which,uint32 what,BHandler *target); + + void DetachedFromWindow(); + + int32 RecentsCount(); + + private: + virtual bool StartBuildingItemList(); + virtual bool AddNextItem(); + bool AddRecents(int32 count); + virtual void DoneBuildingItemList(); + virtual void ClearMenuBuildingState(); + + private: + int32 fWhich; + int32 fRecentsCount; + + int32 fItemIndex; + BMessage fRecentList; +}; + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/FilePanel.cpp b/src/kits/tracker/FilePanel.cpp new file mode 100644 index 0000000000..49c587ca8b --- /dev/null +++ b/src/kits/tracker/FilePanel.cpp @@ -0,0 +1,347 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// Implementation for the public FilePanel object. + +#include +#include + +#include "AutoLock.h" +#include "Commands.h" +#include "FilePanelPriv.h" + +// prototypes for some private kernel calls that will some day be public +#if B_BEOS_VERSION_DANO +#define _IMPEXP_ROOT +#endif +extern "C" _IMPEXP_ROOT int _kset_fd_limit_(int num); +#if B_BEOS_VERSION_DANO +#undef _IMPEXP_ROOT +#endif + +void +run_open_panel() +{ + (new TFilePanel())->Show(); +} + +void +run_save_panel() +{ + (new TFilePanel(B_SAVE_PANEL))->Show(); +} + + +BFilePanel::BFilePanel(file_panel_mode mode, BMessenger *target, + const entry_ref *ref, uint32 nodeFlavors, bool multipleSelection, + BMessage *message, BRefFilter *filter, bool modal, + bool hideWhenDone) +{ + // boost file descriptor limit so file panels in other apps don't have + // problems + _kset_fd_limit_ (512); + BEntry startDir(ref); + fWindow = new TFilePanel(mode, target, &startDir, nodeFlavors, + multipleSelection, message, filter, 0, B_DOCUMENT_WINDOW_LOOK, + modal ? B_MODAL_APP_WINDOW_FEEL : B_NORMAL_WINDOW_FEEL, + hideWhenDone); + + static_cast(fWindow)->SetClientObject(this); + + fWindow->SetIsFilePanel(true); +} + +BFilePanel::~BFilePanel() +{ + if (fWindow->Lock()) + fWindow->Quit(); +} + +void +BFilePanel::Show() +{ + AutoLock lock(fWindow); + if (!lock) + return; + + // if the window is already showing, don't jerk the workspaces around, + // just pull it to us + uint32 workspace = 1UL << (uint32)current_workspace(); + uint32 windowWorkspaces = fWindow->Workspaces(); + if (!(windowWorkspaces & workspace)) + // window in a different workspace, reopen in current + fWindow->SetWorkspaces(workspace); + + if (!IsShowing()) + fWindow->Show(); + + fWindow->Activate(); +} + +void +BFilePanel::Hide() +{ + AutoLock lock(fWindow); + if (!lock) + return; + + if (!fWindow->IsHidden()) + fWindow->QuitRequested(); +} + +bool +BFilePanel::IsShowing() const +{ + AutoLock lock(fWindow); + if (!lock) + return false; + + return !fWindow->IsHidden(); +} + + +void +BFilePanel::SendMessage(const BMessenger *messenger, BMessage *message) +{ + messenger->SendMessage(message); +} + +file_panel_mode +BFilePanel::PanelMode() const +{ + AutoLock lock(fWindow); + if (!lock) + return B_OPEN_PANEL; + + if (static_cast(fWindow)->IsSavePanel()) + return B_SAVE_PANEL; + + return B_OPEN_PANEL; +} + +BMessenger +BFilePanel::Messenger() const +{ + BMessenger target; + + AutoLock lock(fWindow); + if (!lock) + return target; + + return *static_cast(fWindow)->Target(); +} + +void +BFilePanel::SetTarget(BMessenger target) +{ + AutoLock lock(fWindow); + if (!lock) + return; + + static_cast(fWindow)->SetTarget(target); +} + +void +BFilePanel::SetMessage(BMessage *message) +{ + AutoLock lock(fWindow); + if (!lock) + return; + + static_cast(fWindow)->SetMessage(message); +} + +void +BFilePanel::Refresh() +{ + AutoLock lock(fWindow); + if (!lock) + return; + + static_cast(fWindow)->Refresh(); +} + +BRefFilter * +BFilePanel::RefFilter() const +{ + AutoLock lock(fWindow); + if (!lock) + return 0; + + return static_cast(fWindow)->Filter(); +} + +void +BFilePanel::SetRefFilter(BRefFilter *filter) +{ + AutoLock lock(fWindow); + if (!lock) + return; + + static_cast(fWindow)->SetRefFilter(filter); +} + +void +BFilePanel::SetButtonLabel(file_panel_button button, const char *text) +{ + AutoLock lock(fWindow); + if (!lock) + return; + + static_cast(fWindow)->SetButtonLabel(button, text); +} + +void +BFilePanel::GetPanelDirectory(entry_ref *ref) const +{ + AutoLock lock(fWindow); + if (!lock) + return; + + *ref = *static_cast(fWindow)->TargetModel()->EntryRef(); +} + +void +BFilePanel::SetSaveText(const char *text) +{ + AutoLock lock(fWindow); + if (!lock) + return; + + static_cast(fWindow)->SetSaveText(text); +} + +void +BFilePanel::SetPanelDirectory(const entry_ref *ref) +{ + AutoLock lock(fWindow); + if (!lock) + return; + + static_cast(fWindow)->SetTo(ref); +} + +void +BFilePanel::SetPanelDirectory(const char *path) +{ + entry_ref ref; + status_t err = get_ref_for_path(path, &ref); + if (err < B_OK) + return; + + AutoLock lock(fWindow); + if (!lock) + return; + + static_cast(fWindow)->SetTo(&ref); +} + +void +BFilePanel::SetPanelDirectory(const BEntry *entry) +{ + entry_ref ref; + + if (entry && entry->GetRef(&ref) == B_OK) + SetPanelDirectory(&ref); +} + +void +BFilePanel::SetPanelDirectory(const BDirectory *dir) +{ + BEntry entry; + + if (dir && (dir->GetEntry(&entry) == B_OK)) + SetPanelDirectory(&entry); +} + +BWindow * +BFilePanel::Window() const +{ + return fWindow; +} + +void +BFilePanel::Rewind() +{ + AutoLock lock(fWindow); + if (!lock) + return; + + static_cast(fWindow)->Rewind(); +} + +status_t +BFilePanel::GetNextSelectedRef(entry_ref *ref) +{ + AutoLock lock(fWindow); + if (!lock) + return B_ERROR; + + return static_cast(fWindow)->GetNextEntryRef(ref); + +} + + +void +BFilePanel::SetHideWhenDone(bool on) +{ + AutoLock lock(fWindow); + if (!lock) + return; + + static_cast(fWindow)->SetHideWhenDone(on); +} + +bool +BFilePanel::HidesWhenDone(void) const +{ + AutoLock lock(fWindow); + if (!lock) + return false; + + return static_cast(fWindow)->HidesWhenDone(); +} + +void +BFilePanel::WasHidden() +{ + // hook function +} + +void +BFilePanel::SelectionChanged() +{ + // hook function +} + diff --git a/src/kits/tracker/FilePanelPriv.cpp b/src/kits/tracker/FilePanelPriv.cpp new file mode 100644 index 0000000000..d713f171f2 --- /dev/null +++ b/src/kits/tracker/FilePanelPriv.cpp @@ -0,0 +1,1759 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Attributes.h" +#include "AttributeStream.h" +#include "AutoLock.h" +#include "Commands.h" +#include "DesktopPoseView.h" +#include "DirMenu.h" +#include "FavoritesConfig.h" +#include "FavoritesMenu.h" +#include "FilePanelPriv.h" +#include "FSUtils.h" +#include "FSClipboard.h" +#include "IconMenuItem.h" +#include "MimeTypes.h" +#include "NavMenu.h" +#include "PoseView.h" +#include "Tracker.h" +#include "tracker_private.h" + + +const char *kDefaultFilePanelTemplate = "FilePanelSettings"; + + +static uint32 +GetLinkFlavor(const Model *model, bool resolve = true) +{ + if (model && model->IsSymLink()) { + if (!resolve) + return B_SYMLINK_NODE; + model = model->LinkTo(); + } + if (!model) + return 0; + + if (model->IsDirectory()) + return B_DIRECTORY_NODE; + + return B_FILE_NODE; +} + + +static filter_result +key_down_filter(BMessage *message, BHandler **, BMessageFilter *filter) +{ + TFilePanel *panel = dynamic_cast(filter->Looper()); + ASSERT(panel); + BPoseView *view = panel->PoseView(); + + if (panel->TrackingMenu()) + return B_DISPATCH_MESSAGE; + + uchar key; + if (message->FindInt8("byte", (int8 *)&key) != B_OK) + return B_DISPATCH_MESSAGE; + + int32 modifier = 0; + message->FindInt32("modifiers", &modifier); + if (!modifier && key == B_ESCAPE) { + if (view->ActivePose()) + view->CommitActivePose(false); + else + filter->Looper()->PostMessage(kCancelButton); + return B_SKIP_MESSAGE; + } + + if (key == B_RETURN && view->ActivePose()) { + view->CommitActivePose(); + return B_SKIP_MESSAGE; + } + + return B_DISPATCH_MESSAGE; +} + + +// #pragma mark - + + +TFilePanel::TFilePanel(file_panel_mode mode, BMessenger *target, + const BEntry *startDir, uint32 nodeFlavors, bool multipleSelection, + BMessage *message, BRefFilter *filter, uint32 containerWindowFlags, + window_look look, window_feel feel, bool hideWhenDone) + : BContainerWindow(0, containerWindowFlags, look, feel, 0, B_CURRENT_WORKSPACE), + fDirMenu(NULL), + fDirMenuField(NULL), + fTextControl(NULL), + fClientObject(NULL), + fSelectionIterator(0), + fMessage(NULL), + fHideWhenDone(hideWhenDone), + fIsTrackingMenu(false), + fConfigWindow(NULL) +{ + InitIconPreloader(); + + fIsSavePanel = (mode == B_SAVE_PANEL); + + BRect windRect(85, 50, 510, 296); + MoveTo(windRect.LeftTop()); + ResizeTo(windRect.Width(), windRect.Height()); + + fNodeFlavors = (nodeFlavors == 0) ? B_FILE_NODE : nodeFlavors; + + if (target) + fTarget = *target; + else + fTarget = BMessenger(be_app); + + if (message) + SetMessage(message); + else if (fIsSavePanel) + fMessage = new BMessage(B_SAVE_REQUESTED); + else + fMessage = new BMessage(B_REFS_RECEIVED); + + // check for legal starting directory + Model *model = new Model(); + bool useRoot = true; + + if (startDir) { + if (model->SetTo(startDir) == B_OK && model->IsDirectory()) + useRoot = false; + else { + delete model; + model = new Model(); + } + } + + if (useRoot) { + BPath path; + if (find_directory(B_USER_DIRECTORY, &path) == B_OK) { + BEntry entry(path.Path(), true); + if (entry.InitCheck() == B_OK && model->SetTo(&entry) == B_OK) + useRoot = false; + } + } + + if (useRoot) { + BVolume volume; + BDirectory root; + BVolumeRoster volumeRoster; + volumeRoster.GetBootVolume(&volume); + volume.GetRootDirectory(&root); + + BEntry entry; + root.GetEntry(&entry); + model->SetTo(&entry); + } + + fTaskLoop = new PiggybackTaskLoop; + + AutoLock lock(this); + CreatePoseView(model); + fPoseView->SetRefFilter(filter); + if (!fIsSavePanel) + fPoseView->SetMultipleSelection(multipleSelection); + + fPoseView->SetFlags(fPoseView->Flags() | B_NAVIGABLE); + fPoseView->SetPoseEditing(false); + AddCommonFilter(new BMessageFilter(B_KEY_DOWN, key_down_filter)); + AddCommonFilter(new BMessageFilter(B_SIMPLE_DATA, TFilePanel::MessageDropFilter)); + AddCommonFilter(new BMessageFilter(B_NODE_MONITOR, TFilePanel::FSFilter)); + + // inter-application observing + BMessenger tracker(kTrackerSignature); + BHandler::StartWatching(tracker, kDesktopFilePanelRootChanged); + + Init(); +} + + +TFilePanel::~TFilePanel() +{ + // regardless of the hide/close method + // always get rid of the config window + if (fConfigWindow) { + // moved from QuitRequested to ensure that + // if the config window is showing that + // it gets closed as well + fConfigWindow->Lock(); + fConfigWindow->Quit(); + } + + BMessenger tracker(kTrackerSignature); + BHandler::StopWatching(tracker, kDesktopFilePanelRootChanged); + + delete fMessage; +} + + +filter_result +TFilePanel::MessageDropFilter(BMessage *message, BHandler **, BMessageFilter *filter) +{ + TFilePanel *panel = dynamic_cast(filter->Looper()); + if (panel == NULL || !message->WasDropped()) + return B_SKIP_MESSAGE; + + uint32 type; + int32 count; + if (message->GetInfo("refs", &type, &count) != B_OK) + return B_SKIP_MESSAGE; + + if (count != 1) + return B_SKIP_MESSAGE; + + entry_ref ref; + if (message->FindRef("refs", &ref) != B_OK) + return B_SKIP_MESSAGE; + + BEntry entry(&ref); + if (entry.InitCheck() != B_OK) + return B_SKIP_MESSAGE; + + // if the entry is a symlink + // resolve it and see if it is a directory + // pass it on if it is + if (entry.IsSymLink()) { + entry_ref resolvedRef; + + entry.GetRef(&resolvedRef); + BEntry resolvedEntry(&resolvedRef, true); + + if (resolvedEntry.IsDirectory()) { + // both entry and ref need to be the correct locations + // for the last setto + resolvedEntry.GetRef(&ref); + entry.SetTo(&ref); + } + } + + // if not a directory, set to the parent, and select the child + if (!entry.IsDirectory()) { + node_ref child; + if (entry.GetNodeRef(&child) != B_OK) + return B_SKIP_MESSAGE; + + BPath path(&entry); + + if (entry.GetParent(&entry) != B_OK) + return B_SKIP_MESSAGE; + + entry.GetRef(&ref); + + panel->fTaskLoop->RunLater(NewMemberFunctionObjectWithResult + (&TFilePanel::SelectChildInParent, panel, + const_cast(&ref), + const_cast(&child)), + ref == *panel->TargetModel()->EntryRef() ? 0 : 100000, 200000, 5000000); + // if the target directory is already current, we won't + // delay the initial selection try + + // also set the save name to the dragged in entry + if (panel->IsSavePanel()) + panel->SetSaveText(path.Leaf()); + } + + panel->SetTo(&ref); + + return B_SKIP_MESSAGE; +} + + +filter_result +TFilePanel::FSFilter(BMessage *message, BHandler **, BMessageFilter *filter) +{ + switch (message->FindInt32("opcode")) { + case B_ENTRY_MOVED: + { + node_ref itemNode; + node_ref dirNode; + TFilePanel *panel = dynamic_cast(filter->Looper()); + + message->FindInt32("device", &dirNode.device); + itemNode.device = dirNode.device; + message->FindInt64("to directory", (int64 *)&dirNode.node); + message->FindInt64("node", (int64 *)&itemNode.node); + const char *name; + if (message->FindString("name", &name) != B_OK) + break; + + // if current directory moved, update entry ref and menu + // but not wind title + if (*(panel->TargetModel()->NodeRef()) == itemNode) { + panel->TargetModel()->UpdateEntryRef(&dirNode, name); + panel->SetTo(panel->TargetModel()->EntryRef()); + return B_SKIP_MESSAGE; + } + break; + } + case B_ENTRY_REMOVED: + { + node_ref itemNode; + TFilePanel *panel = dynamic_cast(filter->Looper()); + message->FindInt32("device", &itemNode.device); + message->FindInt64("node", (int64 *)&itemNode.node); + + // if folder we're watching is deleted, switch to root + // or Desktop + if (*(panel->TargetModel()->NodeRef()) == itemNode) { + BVolumeRoster volumeRoster; + BVolume volume; + volumeRoster.GetBootVolume(&volume); + + BDirectory root; + volume.GetRootDirectory(&root); + + BEntry entry; + entry_ref ref; + root.GetEntry(&entry); + entry.GetRef(&ref); + + panel->SwitchDirToDesktopIfNeeded(ref); + + panel->SetTo(&ref); + return B_SKIP_MESSAGE; + } + } + break; + } + return B_DISPATCH_MESSAGE; +} + + +void +TFilePanel::DispatchMessage(BMessage *message, BHandler *handler) +{ + _inherited::DispatchMessage(message, handler); + if (message->what == B_KEY_DOWN || message->what == B_MOUSE_DOWN) + AdjustButton(); +} + + +BFilePanelPoseView * +TFilePanel::PoseView() const +{ + ASSERT(dynamic_cast(fPoseView)); + return static_cast(fPoseView); +} + + +bool +TFilePanel::QuitRequested() +{ + // If we have a client object then this window will simply hide + // itself, to be closed later when the client object itself is + // destroyed. If we have no client then we must have been started + // from the "easy" functions which simply instantiate a TFilePanel + // and expect it to go away by itself + + if (fClientObject) { + Hide(); + if (fClientObject) + fClientObject->WasHidden(); + + BMessage message(*fMessage); + message.what = B_CANCEL; + message.AddInt32("old_what", (int32)fMessage->what); + message.AddPointer("source", fClientObject); + fTarget.SendMessage(&message); + return false; + } + + return _inherited::QuitRequested(); +} + + +BRefFilter * +TFilePanel::Filter() const +{ + return fPoseView->RefFilter(); +} + + +void +TFilePanel::SetTarget(BMessenger target) +{ + fTarget = target; +} + + +void +TFilePanel::SetMessage(BMessage *message) +{ + delete fMessage; + fMessage = new BMessage(*message); +} + + +void +TFilePanel::SetRefFilter(BRefFilter *filter) +{ + if (!filter) + return; + + fPoseView->SetRefFilter(filter); + fPoseView->CommitActivePose(); + fPoseView->Refresh(); +} + + +void +TFilePanel::SetTo(const entry_ref *ref) +{ + if (!ref) + return; + + entry_ref setToRef(*ref); + + bool isDesktop = SwitchDirToDesktopIfNeeded(setToRef); + + BEntry entry(&setToRef); + if (entry.InitCheck() != B_OK || !entry.IsDirectory()) + return; + + SwitchDirMenuTo(&setToRef); + + PoseView()->SetIsDesktop(isDesktop); + fPoseView->SwitchDir(&setToRef); + + AddShortcut('H', B_COMMAND_KEY, new BMessage(kSwitchToHome)); + // our shortcut got possibly removed because the home + // menu item got removed - we shouldn't really have to do + // this - this is a workaround for a kit bug. +} + + +void +TFilePanel::Rewind() +{ + fSelectionIterator = 0; +} + + +void +TFilePanel::SetClientObject(BFilePanel *panel) +{ + fClientObject = panel; +} + + +void +TFilePanel::AdjustButton() +{ + // adjust button state + BButton *button = dynamic_cast(FindView("default button")); + if (!button) + return; + + BTextControl *textControl = dynamic_cast(FindView("text view")); + BObjectList *selectionList = fPoseView->SelectionList(); + const char *buttonText = fButtonText.String(); + bool enabled = false; + + if (fIsSavePanel && textControl) { + enabled = textControl->Text()[0] != '\0'; + if (fPoseView->IsFocus()) { + fPoseView->ShowSelection(true); + if (selectionList->CountItems() == 1) { + Model *model = selectionList->FirstItem()->TargetModel(); + if (model->ResolveIfLink()->IsDirectory()) { + enabled = true; + buttonText = "Open"; + } else { + // insert the name of the selected model into the text field + textControl->SetText(model->Name()); + textControl->MakeFocus(true); + } + } + } else + fPoseView->ShowSelection(false); + } else { + int32 count = selectionList->CountItems(); + if (count) { + enabled = true; + + // go through selection list looking at content + for (int32 index = 0; index < count; index++) { + Model *model = selectionList->ItemAt(index)->TargetModel(); + + uint32 modelFlavor = GetLinkFlavor(model, false); + uint32 linkFlavor = GetLinkFlavor(model, true); + + // if only one item is selected and we're not in dir + // selection mode then we don't disable button ever + if ((modelFlavor == B_DIRECTORY_NODE + || linkFlavor == B_DIRECTORY_NODE) + && count == 1) + break; + + if ((fNodeFlavors & modelFlavor) == 0 + && (fNodeFlavors & linkFlavor) == 0) { + enabled = false; + break; + } + } + } + } + + button->SetLabel(buttonText); + button->SetEnabled(enabled); +} + + +void +TFilePanel::SelectionChanged() +{ + AdjustButton(); + + if (fClientObject) + fClientObject->SelectionChanged(); +} + + +status_t +TFilePanel::GetNextEntryRef(entry_ref *ref) +{ + if (!ref) + return B_ERROR; + + BPose *pose = fPoseView->SelectionList()->ItemAt(fSelectionIterator++); + if (!pose) + return B_ERROR; + + *ref = *pose->TargetModel()->EntryRef(); + return B_OK; +} + + +BPoseView * +TFilePanel::NewPoseView(Model *model, BRect rect, uint32) +{ + return new BFilePanelPoseView(model, rect); +} + + +void +TFilePanel::Init(const BMessage *) +{ + BRect windRect(Bounds()); + AddChild(fBackView = new BackgroundView(windRect)); + + // add poseview menu bar + fMenuBar = new BMenuBar(BRect(0, 0, windRect.Width(), 1), "MenuBar"); + fMenuBar->SetBorder(B_BORDER_FRAME); + fBackView->AddChild(fMenuBar); + + AddMenus(); + AddContextMenus(); + + FavoritesMenu *favorites = new FavoritesMenu("Favorites", + new BMessage(kSwitchDirectory), new BMessage(B_REFS_RECEIVED), + BMessenger(this), IsSavePanel()); + favorites->AddItem(new BMenuItem("Add Current Folder", + new BMessage(kAddCurrentDir))); + favorites->AddItem(new BMenuItem("Configure Favorites"B_UTF8_ELLIPSIS, + new BMessage(kConfigShow))); + + fMenuBar->AddItem(favorites); + + // configure menus + BMenuItem *item = fMenuBar->FindItem("Window"); + if (item) { + fMenuBar->RemoveItem(item); + delete item; + } + + item = fMenuBar->FindItem("File"); + if (item) { + BMenu *menu = item->Submenu(); + if (menu) { + item = menu->FindItem(kOpenSelection); + if (item && menu->RemoveItem(item)) + delete item; + + item = menu->FindItem(kDuplicateSelection); + if (item && menu->RemoveItem(item)) + delete item; + + // remove add-ons menu, identifier menu, separator + item = menu->FindItem(kAddOnsMenuName); + if (item) { + int32 index = menu->IndexOf(item); + delete menu->RemoveItem(index); + delete menu->RemoveItem(--index); + delete menu->RemoveItem(--index); + } + + // remove separator + item = menu->FindItem(B_CUT); + if (item) { + item = menu->ItemAt(menu->IndexOf(item)-1); + if (item && menu->RemoveItem(item)) + delete item; + } + } + } + + // add directory menu and menufield + fDirMenu = new BDirMenu(0, kSwitchDirectory, "refs"); + + font_height ht; + be_plain_font->GetHeight(&ht); + float f_height = ht.ascent + ht.descent + ht.leading; + + BRect rect; + rect.top = fMenuBar->Bounds().Height() + 2; + rect.left = windRect.left + 9; + rect.right = rect.left + 300; + rect.bottom = rect.top + (f_height > 22 ? f_height : 22); + + fDirMenuField = new BMenuField(rect, "DirMenuField", "", fDirMenu); + fDirMenuField->MenuBar()->SetFont(be_plain_font); + fDirMenuField->SetDivider(0); + + fDirMenuField->MenuBar()->RemoveItem((int32)0); + fDirMenu->SetMenuBar(fDirMenuField->MenuBar()); + // the above is a weird call from BDirMenu + // ToDo: clean up + + BEntry entry(TargetModel()->EntryRef()); + if (entry.InitCheck() == B_OK) + fDirMenu->Populate(&entry, 0, true, true, false, true); + else + fDirMenu->Populate(0, 0, true, true, false, true); + + fBackView->AddChild(fDirMenuField); + + // add file name text view + if (fIsSavePanel) { + BRect rect(windRect); + rect.top = rect.bottom - 35; + rect.left = 9; + rect.right = rect.left + 170; + rect.bottom = rect.top + 13; + + fTextControl = new BTextControl(rect, "text view", "save text", "", NULL, + B_FOLLOW_LEFT | B_FOLLOW_BOTTOM); + DisallowMetaKeys(fTextControl->TextView()); + DisallowFilenameKeys(fTextControl->TextView()); + fBackView->AddChild(fTextControl); + fTextControl->SetDivider(0.0f); + fTextControl->TextView()->SetMaxBytes(B_FILE_NAME_LENGTH - 1); + + fButtonText = "Save"; + } else + fButtonText = "Open"; + + rect = windRect; + rect.OffsetTo(10, fMenuBar->Bounds().Height() * 2 + 16); + rect.bottom = windRect.bottom - 60; + rect.right -= B_V_SCROLL_BAR_WIDTH + 20; + + // re-parent the poseview to our backview + // ToDo: + // This is terrible, fix it up + PoseView()->RemoveSelf(); + if (fIsSavePanel) + fBackView->AddChild(PoseView(), fTextControl); + else + fBackView->AddChild(PoseView()); + + PoseView()->MoveTo(rect.LeftTop()); + PoseView()->ResizeTo(rect.Width(), rect.Height()); + PoseView()->AddScrollBars(); + PoseView()->SetDragEnabled(false); + PoseView()->SetDropEnabled(false); + PoseView()->SetSelectionHandler(this); + PoseView()->SetSelectionChangedHook(true); + PoseView()->DisableSaveLocation(); + + + AddShortcut('W', B_COMMAND_KEY, new BMessage(kCancelButton)); + AddShortcut('H', B_COMMAND_KEY, new BMessage(kSwitchToHome)); + AddShortcut(B_DOWN_ARROW, B_COMMAND_KEY, new BMessage(kOpenDir)); + AddShortcut(B_DOWN_ARROW, B_COMMAND_KEY | B_OPTION_KEY, new BMessage(kOpenDir)); + AddShortcut(B_UP_ARROW, B_COMMAND_KEY, new BMessage(kOpenParentDir)); + AddShortcut(B_UP_ARROW, B_COMMAND_KEY | B_OPTION_KEY, new BMessage(kOpenParentDir)); + + // New code to make buttons font sensitive + rect = windRect; + rect.top = rect.bottom - 35; + rect.bottom -= 10; + rect.right -= 25; + float default_width = be_plain_font->StringWidth(fButtonText.String()) + 20; + rect.left = (default_width > 75) ? (rect.right - default_width) : (rect.right - 75); + + BButton *default_button = new BButton(rect, "default button", fButtonText.String(), + new BMessage(kDefaultButton), B_FOLLOW_RIGHT + B_FOLLOW_BOTTOM); + fBackView->AddChild(default_button); + + rect.right = rect.left -= 10; + float cancel_width = be_plain_font->StringWidth("Cancel") + 20; + rect.left = (cancel_width > 75) ? (rect.right - cancel_width) : (rect.right - 75); + + BButton *cancel_button = new BButton(rect, "cancel button", "Cancel", + new BMessage(kCancelButton), B_FOLLOW_RIGHT + B_FOLLOW_BOTTOM); + fBackView->AddChild(cancel_button); + + if (!fIsSavePanel) + default_button->SetEnabled(false); + + default_button->MakeDefault(true); + + RestoreState(); + + PoseView()->ScrollTo(B_ORIGIN); + PoseView()->UpdateScrollRange(); + PoseView()->ScrollTo(B_ORIGIN); + + if (fTextControl) { + fTextControl->MakeFocus(); + fTextControl->TextView()->SelectAll(); + } else + PoseView()->MakeFocus(); + + app_info info; + BString title; + if (be_app->GetAppInfo(&info) == B_OK) + title << info.ref.name << ": "; + + title << fButtonText; // Open or Save + + SetTitle(title.String()); + + SetSizeLimits(360, 10000, 200, 10000); +} + + +void +TFilePanel::RestoreState() +{ + BNode defaultingNode; + if (DefaultStateSourceNode(kDefaultFilePanelTemplate, &defaultingNode, false)) { + AttributeStreamFileNode streamNodeSource(&defaultingNode); + RestoreWindowState(&streamNodeSource); + PoseView()->Init(&streamNodeSource); + } else { + RestoreWindowState(NULL); + PoseView()->Init(NULL); + } +} + + +void +TFilePanel::SaveState(bool) +{ + BNode defaultingNode; + if (DefaultStateSourceNode(kDefaultFilePanelTemplate, &defaultingNode, + true, false)) { + AttributeStreamFileNode streamNodeDestination(&defaultingNode); + SaveWindowState(&streamNodeDestination); + PoseView()->SaveState(&streamNodeDestination); + } +} + + +void +TFilePanel::SaveState(BMessage &message) const +{ + _inherited::SaveState(message); +} + + +void +TFilePanel::RestoreWindowState(AttributeStreamNode *node) +{ + SetSizeLimits(360, 10000, 200, 10000); + if (!node) + 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 +TFilePanel::RestoreState(const BMessage &message) +{ + _inherited::RestoreState(message); +} + + +void +TFilePanel::RestoreWindowState(const BMessage &message) +{ + _inherited::RestoreWindowState(message); +} + + +void +TFilePanel::AddFileContextMenus(BMenu *menu) +{ + menu->AddItem(new BMenuItem("Get Info", new BMessage(kGetInfo), 'I')); + menu->AddItem(new BMenuItem("Edit Name", new BMessage(kEditItem), 'E')); + menu->AddItem(new BMenuItem(TrackerSettings().DontMoveFilesToTrash() ? + "Delete" : "Move to Trash", + new BMessage(kMoveToTrash), 'T')); + menu->AddSeparatorItem(); + menu->AddItem(new BMenuItem("Cut", new BMessage(B_CUT), 'X')); + menu->AddItem(new BMenuItem("Copy", new BMessage(B_COPY), 'C')); +// menu->AddItem(pasteItem = new BMenuItem("Paste", new BMessage(B_PASTE), 'V')); + + menu->SetTargetForItems(PoseView()); +} + + +void +TFilePanel::AddVolumeContextMenus(BMenu *menu) +{ + menu->AddItem(new BMenuItem("Open", new BMessage(kOpenSelection), 'O')); + menu->AddItem(new BMenuItem("Get Info", new BMessage(kGetInfo), 'I')); + menu->AddItem(new BMenuItem("Edit Name", new BMessage(kEditItem), 'E')); + menu->AddSeparatorItem(); + menu->AddItem(new BMenuItem("Cut", new BMessage(B_CUT), 'X')); + menu->AddItem(new BMenuItem("Copy", new BMessage(B_COPY), 'C')); +// menu->AddItem(pasteItem = new BMenuItem("Paste", new BMessage(B_PASTE), 'V')); + + menu->SetTargetForItems(PoseView()); +} + + +void +TFilePanel::AddWindowContextMenus(BMenu *menu) +{ + BMenuItem *item = new BMenuItem("New Folder", new BMessage(kNewFolder), 'N'); + item->SetTarget(PoseView()); + menu->AddItem(item); + menu->AddSeparatorItem(); + + item = new BMenuItem("Paste", new BMessage(B_PASTE), 'V'); + item->SetTarget(PoseView()); + menu->AddItem(item); + menu->AddSeparatorItem(); + + item = new BMenuItem("Select"B_UTF8_ELLIPSIS, new BMessage(kShowSelectionWindow), + 'A', B_SHIFT_KEY); + item->SetTarget(PoseView()); + menu->AddItem(item); + + item = new BMenuItem("Select All", new BMessage(B_SELECT_ALL), 'A'); + item->SetTarget(PoseView()); + menu->AddItem(item); + + item = new BMenuItem("Invert Selection", new BMessage(kInvertSelection), 'S'); + item->SetTarget(PoseView()); + menu->AddItem(item); + + item = new BMenuItem("Go To Parent", new BMessage(kOpenParentDir), B_UP_ARROW); + item->SetTarget(this); + menu->AddItem(item); +} + + +void +TFilePanel::AddDropContextMenus(BMenu *) +{ +} + + +void +TFilePanel::MenusBeginning() +{ + int32 count = PoseView()->SelectionList()->CountItems(); + + EnableNamedMenuItem(fMenuBar, kNewFolder, !TargetModel()->IsRoot()); + EnableNamedMenuItem(fMenuBar, kMoveToTrash, !TargetModel()->IsRoot() && count); + EnableNamedMenuItem(fMenuBar, kGetInfo, count != 0); + EnableNamedMenuItem(fMenuBar, kEditItem, count == 1); + + SetCutItem(fMenuBar); + SetCopyItem(fMenuBar); + SetPasteItem(fMenuBar); + + fIsTrackingMenu = true; +} + + +void +TFilePanel::MenusEnded() +{ + fIsTrackingMenu = false; +} + + +void +TFilePanel::ShowContextMenu(BPoint point, const entry_ref *ref, BView *view) +{ + EnableNamedMenuItem(fWindowContextMenu, kNewFolder, !TargetModel()->IsRoot()); + EnableNamedMenuItem(fWindowContextMenu, kOpenParentDir, !TargetModel()->IsRoot()); + EnableNamedMenuItem(fWindowContextMenu, kMoveToTrash, !TargetModel()->IsRoot()); + + _inherited::ShowContextMenu(point, ref, view); +} + + +void +TFilePanel::SetupNavigationMenu(const entry_ref *, BMenu *) +{ + // do nothing here so nav menu doesn't get added +} + + +void +TFilePanel::SetButtonLabel(file_panel_button selector, const char *text) +{ + switch (selector) { + case B_CANCEL_BUTTON: + { + BButton *button = dynamic_cast(FindView("cancel button")); + if (!button) + break; + + float old_width = button->StringWidth(button->Label()); + button->SetLabel(text); + float delta = old_width - button->StringWidth(text); + if (delta) { + button->MoveBy(delta, 0); + button->ResizeBy(-delta, 0); + } + } + break; + + case B_DEFAULT_BUTTON: + { + fButtonText = text; + float delta = 0; + BButton *button = dynamic_cast(FindView("default button")); + if (button) { + float old_width = button->StringWidth(button->Label()); + button->SetLabel(text); + delta = old_width - button->StringWidth(text); + if (delta) { + button->MoveBy(delta, 0); + button->ResizeBy(-delta, 0); + } + } + + // now must move cancel button + button = dynamic_cast(FindView("cancel button")); + if (button) + button->MoveBy(delta, 0); + } + break; + } +} + + +void +TFilePanel::SetSaveText(const char *text) +{ + if (!text) + return; + + BTextControl *textControl = dynamic_cast(FindView("text view")); + textControl->SetText(text); + textControl->TextView()->SelectAll(); +} + + +void +TFilePanel::MessageReceived(BMessage *message) +{ + entry_ref ref; + + switch (message->what) { + case B_REFS_RECEIVED: + // item was double clicked in file panel (PoseView) + if (message->FindRef("refs", &ref) == B_OK) { + + BEntry entry(&ref, true); + if (entry.InitCheck() == B_OK) { + + // Double-click on dir or link-to-dir ALWAYS opens the dir. + // If more than one dir is selected, the + // first is entered. + if (entry.IsDirectory()) { + entry.GetRef(&ref); + bool isDesktop = SwitchDirToDesktopIfNeeded(ref); + + PoseView()->SetIsDesktop(isDesktop); + entry.SetTo(&ref); + PoseView()->SwitchDir(&ref); + SwitchDirMenuTo(&ref); + } else { + + // Otherwise, we have a file or a link to a file. + // AdjustButton has already tested the flavor; + // all we have to do is see if the button is enabled. + BButton *button = dynamic_cast(FindView("default button")); + if (!button) + break; + + if (IsSavePanel()) { + int32 count = 0; + type_code type; + message->GetInfo("refs", &type, &count); + + // Don't allow saves of multiple files + if (count > 1) + ShowCenteredAlert("Sorry, saving of more than one item is not allowed.", + "Cancel"); + else { + // if we are a savepanel, set up the filepanel correctly + // then pass control so we follow the same path as if the user + // clicked the save button + + // set the 'name' fld to the current ref's name + // notify the panel that the default button should be enabled + SetSaveText(ref.name); + SelectionChanged(); + + HandleSaveButton(); + } + break; + } + + + // send handler a message and close + BMessage openMessage(*fMessage); + for (int32 index = 0; ; index++) { + if (message->FindRef("refs", index, &ref) != B_OK) + break; + openMessage.AddRef("refs", &ref); + } + OpenSelectionCommon(&openMessage); + } + } + } + break; + + case kSwitchDirectory: + { + entry_ref ref; + // this comes from dir menu or nav menu, so switch directories + if (message->FindRef("refs", &ref) == B_OK) { + BEntry entry(&ref, true); + if (entry.GetRef(&ref) == B_OK) + SetTo(&ref); + } + } + break; + + case kSwitchToHome: + { + BPath homePath; + entry_ref ref; + if (find_directory(B_USER_DIRECTORY, &homePath) != B_OK + || get_ref_for_path(homePath.Path(), &ref) != B_OK) + break; + + SetTo(&ref); + } + break; + + case kAddCurrentDir: + { + BPath path; + if (find_directory (B_USER_SETTINGS_DIRECTORY, &path, true) != B_OK) + break; + + path.Append(kGoDirectory); + BDirectory goDirectory(path.Path()); + + if (goDirectory.InitCheck() == B_OK) { + BEntry entry(TargetModel()->EntryRef()); + entry.GetPath(&path); + + BSymLink link; + goDirectory.CreateSymLink(TargetModel()->Name(), path.Path(), &link); + } + } + break; + + case kConfigShow: + { + if (fConfigWindow) + fConfigWindow->Activate(); + else { + BPath path; + if (find_directory (B_USER_SETTINGS_DIRECTORY, &path, true) != B_OK) + break; + + path.Append(kGoDirectory); + BDirectory goDirectory(path.Path()); + + if (goDirectory.InitCheck() == B_OK) { + entry_ref startref; + BEntry entry; + goDirectory.GetEntry(&entry); + entry.GetRef(&startref); + + int32 apps, docs, folders; + TrackerSettings().RecentCounts(&apps, &docs, &folders); + + // if this is a save panel + // then don't show recent docs controls + if (fIsSavePanel) + docs = -1; + + fConfigWindow = new TFavoritesConfigWindow(BRect(0, 0, 320, 24), + "Configure Favorites", Feel() == B_MODAL_APP_WINDOW_FEEL, + fNodeFlavors, BMessenger(this), &startref, -1, docs, folders); + } + } + } + break; + + case kConfigClose: + { + int32 count = 0; + TrackerSettings settings; + + // save off whatever was last in the fields + // do this just in case someone didn't tab out + if (message->FindInt32("applications", &count) == B_OK) + settings.SetRecentApplicationsCount(count); + if (message->FindInt32("folders", &count) == B_OK) + settings.SetRecentFoldersCount(count); + if (message->FindInt32("documents", &count) == B_OK) + settings.SetRecentDocumentsCount(count); + + settings.SaveSettings(false); + + fConfigWindow = NULL; + } + break; + + case kUpdateAppsCount: + case kUpdateDocsCount: + case kUpdateFolderCount: + { + // messages sent when the user changes the count + int32 count; + TrackerSettings settings; + + if (message->FindInt32("count", &count) == B_OK) { + if (message->what == kUpdateAppsCount) + settings.SetRecentApplicationsCount(count); + else if (message->what == kUpdateDocsCount) + settings.SetRecentDocumentsCount(count); + else if (message->what == kUpdateFolderCount) + settings.SetRecentFoldersCount(count); + settings.SaveSettings(false); + } + } + break; + + case kCancelButton: + PostMessage(B_QUIT_REQUESTED); + break; + + case kOpenDir: + OpenDirectory(); + break; + + case kOpenParentDir: + OpenParent(); + break; + + case kDefaultButton: + if (fIsSavePanel) { + if (PoseView()->IsFocus() + && PoseView()->SelectionList()->CountItems() == 1) { + Model *model = (PoseView()->SelectionList()->FirstItem())->TargetModel(); + if (model->ResolveIfLink()->IsDirectory()) { + PoseView()->CommitActivePose(); + PoseView()->OpenSelection(); + break; + } + } + + HandleSaveButton(); + } else + HandleOpenButton(); + + break; + + case B_OBSERVER_NOTICE_CHANGE: + { + int32 observerWhat; + if (message->FindInt32("be:observe_change_what", &observerWhat) == B_OK) { + switch (observerWhat) { + case kDesktopFilePanelRootChanged: + { + bool desktopIsRoot = true; + message->FindBool("DesktopFilePanelRoot", &desktopIsRoot); + TrackerSettings().SetDesktopFilePanelRoot(desktopIsRoot); + SetTo(TargetModel()->EntryRef()); + break; + } + } + } + } + break; + + default: + _inherited::MessageReceived(message); + } +} + + +void +TFilePanel::OpenDirectory() +{ + BObjectList *list = PoseView()->SelectionList(); + if (list->CountItems() != 1) + return; + + Model *model = list->FirstItem()->TargetModel(); + if (model->ResolveIfLink()->IsDirectory()) { + BMessage message(B_REFS_RECEIVED); + message.AddRef("refs", model->EntryRef()); + PostMessage(&message); + } +} + + +void +TFilePanel::OpenParent() +{ + if (!CanOpenParent()) + return; + + BEntry parentEntry; + BDirectory dir; + + Model oldModel(*PoseView()->TargetModel()); + BEntry entry(oldModel.EntryRef()); + + if (entry.InitCheck() == B_OK + && entry.GetParent(&dir) == B_OK + && dir.GetEntry(&parentEntry) == B_OK + && entry != parentEntry) { + + entry_ref ref; + parentEntry.GetRef(&ref); + + PoseView()->SetIsDesktop(SwitchDirToDesktopIfNeeded(ref)); + PoseView()->SwitchDir(&ref); + SwitchDirMenuTo(&ref); + + // make sure the child get's selected in the new view once it + // shows up + fTaskLoop->RunLater(NewMemberFunctionObjectWithResult + (&TFilePanel::SelectChildInParent, this, + const_cast(&ref), + oldModel.NodeRef()), 100000, 200000, 5000000); + } +} + + +bool +TFilePanel::CanOpenParent() const +{ + if (TrackerSettings().DesktopFilePanelRoot()) { + // don't allow opening Desktop folder's parent + BEntry entry(TargetModel()->EntryRef()); + if (FSIsDeskDir(&entry, TargetModel()->NodeRef()->device)) + return false; + } + + // block on "/" + BEntry root("/"); + node_ref rootRef; + root.GetNodeRef(&rootRef); + + return rootRef != *TargetModel()->NodeRef(); +} + + +bool +TFilePanel::SwitchDirToDesktopIfNeeded(entry_ref &ref) +{ + // support showing Desktop as root of everything + // This call implements the worm hole that maps Desktop as + // a root above the disks + TrackerSettings settings; + if (!settings.DesktopFilePanelRoot()) + // Tracker isn't set up that way, just let Disks show + return false; + + BEntry entry(&ref); + BEntry root("/"); + + BDirectory desktopDir; + BVolume bootVol; + BVolumeRoster().GetBootVolume(&bootVol); + FSGetDeskDir(&desktopDir, bootVol.Device()); + + if ((bootVol.Device() != ref.device && FSIsDeskDir(&entry, ref.device)) + // navigated into non-boot desktop, switch to boot desktop + || (entry == root && !settings.ShowDisksIcon())) { + // hit "/" level, map to desktop + + desktopDir.GetEntry(&entry); + entry.GetRef(&ref); + return true; + } + return FSIsDeskDir(&entry, ref.device); +} + + +bool +TFilePanel::SelectChildInParent(const entry_ref *, const node_ref *child) +{ + AutoLock lock(this); + + if (!IsLocked()) + return false; + + int32 index; + BPose *pose = PoseView()->FindPose(child, &index); + if (!pose) + return false; + + PoseView()->UpdateScrollRange(); + // ToDo: Scroll range should be updated by now, for some + // reason sometimes it is not right, force it here + PoseView()->SelectPose(pose, index, true); + return true; +} + + +int32 +TFilePanel::ShowCenteredAlert(const char *text, const char *button1, + const char *button2, const char *button3) +{ + BAlert *alert = new BAlert("", text, button1, button2, button3, + B_WIDTH_AS_USUAL, B_WARNING_ALERT); + alert->MoveTo(Frame().left + 10, Frame().top + 10); + return alert->Go(); +} + + +void +TFilePanel::HandleSaveButton() +{ + BDirectory dir; + + if (TargetModel()->IsRoot()) { + ShowCenteredAlert("Sorry, you can't save things at the root of " + "your system.", "Cancel"); + return; + } + + // check for some illegal file names + if (strcmp(fTextControl->Text(), ".") == 0 + || strcmp(fTextControl->Text(), "..") == 0) { + ShowCenteredAlert("The name you have specified is illegal. Please type " + "another name.", "Cancel"); + fTextControl->TextView()->SelectAll(); + return; + } + + if (dir.SetTo(TargetModel()->EntryRef()) != B_OK) { + ShowCenteredAlert("There was a problem trying to save in the folder " + "you specified. Please try another one.", "Cancel"); + return; + } + + if (dir.Contains(fTextControl->Text())) { + if (dir.Contains(fTextControl->Text(), B_DIRECTORY_NODE)) { + ShowCenteredAlert("The name you have specified is already the name " + "of a folder. Please type another name.", "Cancel"); + fTextControl->TextView()->SelectAll(); + return; + } else { + // if this was invoked by a dbl click, it is an explicit replacement + // of the file. + BString str; + str << "The file \"" << fTextControl->Text() << "\" already exists in the " + "specified folder. Do you want to replace it?"; + + if (ShowCenteredAlert(str.String(), "Cancel", "Replace") == 0) { + // user canceled + fTextControl->TextView()->SelectAll(); + return; + } + // user selected "Replace" - let app deal with it + } + } + + BMessage message(*fMessage); + message.AddRef("directory", TargetModel()->EntryRef()); + message.AddString("name", fTextControl->Text()); + + if (fClientObject) + fClientObject->SendMessage(&fTarget, &message); + else + fTarget.SendMessage(&message); + + // close window if we're dealing with standard message + if (fHideWhenDone) + PostMessage(B_QUIT_REQUESTED); +} + + +void +TFilePanel::OpenSelectionCommon(BMessage *openMessage) +{ + if (!openMessage->HasRef("refs")) + return; + + for (int32 index = 0; ; index++) { + entry_ref ref; + if (openMessage->FindRef("refs", index, &ref) != B_OK) + break; + + BEntry entry(&ref, true); + if (entry.InitCheck() == B_OK) { + if (entry.IsDirectory()) + BRoster().AddToRecentFolders(&ref); + else + BRoster().AddToRecentDocuments(&ref); + } + } + + BRoster().AddToRecentFolders(TargetModel()->EntryRef()); + + if (fClientObject) + fClientObject->SendMessage(&fTarget, openMessage); + else + fTarget.SendMessage(openMessage); + + // close window if we're dealing with standard message + if (fHideWhenDone) + PostMessage(B_QUIT_REQUESTED); +} + + +void +TFilePanel::HandleOpenButton() +{ + PoseView()->CommitActivePose(); + BObjectList *selection = PoseView()->SelectionList(); + + // if we have only one directory and we're not opening dirs, enter. + if ((fNodeFlavors & B_DIRECTORY_NODE) == 0 + && selection->CountItems() == 1) { + Model *model = selection->FirstItem()->TargetModel(); + + if (model->IsDirectory() + || (model->IsSymLink() && !(fNodeFlavors & B_SYMLINK_NODE) + && model->ResolveIfLink()->IsDirectory())) { + + BMessage message(B_REFS_RECEIVED); + message.AddRef("refs", model->EntryRef()); + PostMessage(&message); + return; + } + } + + // don't do anything unless there are items selected + // message->fMessage->message from here to end + if (selection->CountItems()) { + BMessage message(*fMessage); + // go through selection and add appropriate items + for (int32 index = 0; index < selection->CountItems(); index++) { + Model *model = selection->ItemAt(index)->TargetModel(); + + if (((fNodeFlavors & B_DIRECTORY_NODE) != 0 + && model->ResolveIfLink()->IsDirectory()) + || ((fNodeFlavors & B_SYMLINK_NODE) != 0 && model->IsSymLink()) + || ((fNodeFlavors & B_FILE_NODE) != 0 && model->ResolveIfLink()->IsFile())) + message.AddRef("refs", model->EntryRef()); + } + + OpenSelectionCommon(&message); + } +} + + +void +TFilePanel::SwitchDirMenuTo(const entry_ref *ref) +{ + BEntry entry(ref); + for (int32 index = fDirMenu->CountItems() - 1; index >= 0; index--) + delete fDirMenu->RemoveItem(index); + + fDirMenuField->MenuBar()->RemoveItem((int32)0); + fDirMenu->Populate(&entry, 0, true, true, false, true); + + ModelMenuItem *item = dynamic_cast( + fDirMenuField->MenuBar()->ItemAt(0)); + ASSERT(item); + item->SetEntry(&entry); +} + + +void +TFilePanel::WindowActivated(bool active) +{ + // force focus to update properly + fBackView->Invalidate(); + _inherited::WindowActivated(active); +} + + +BFilePanelPoseView::BFilePanelPoseView(Model *model, BRect frame, uint32 resizeMask) + : BPoseView(model, frame, kListMode, resizeMask), + fIsDesktop(false) +{ +} + + +void +BFilePanelPoseView::StartWatching() +{ + TTracker::WatchNode(0, B_WATCH_MOUNT, this); + + // inter-application observing + BMessenger tracker(kTrackerSignature); + BHandler::StartWatching(tracker, kVolumesOnDesktopChanged); + BHandler::StartWatching(tracker, kDesktopIntegrationChanged); +} + + +void +BFilePanelPoseView::StopWatching() +{ + stop_watching(this); + + // inter-application observing + BMessenger tracker(kTrackerSignature); + BHandler::StopWatching(tracker, kVolumesOnDesktopChanged); + BHandler::StopWatching(tracker, kDesktopIntegrationChanged); +} + + +bool +BFilePanelPoseView::FSNotification(const BMessage *message) +{ + if (IsDesktopView()) { + // Pretty much copied straight from DesktopPoseView. Would be better + // if the code could be shared somehow. + switch (message->FindInt32("opcode")) { + case B_DEVICE_MOUNTED: + { + dev_t device; + if (message->FindInt32("new device", &device) != B_OK) + break; + + ASSERT(TargetModel()); + TrackerSettings settings; + + BVolume volume(device); + if (volume.InitCheck() != B_OK) + break; + + if (settings.MountVolumesOntoDesktop() + && (!volume.IsShared() || settings.MountSharedVolumesOntoDesktop())) { + // place an icon for the volume onto the desktop + CreateVolumePose(&volume, true); + } + + if (!ShouldIntegrateDesktop(volume)) + break; + + BDirectory otherDesktop; + BEntry entry; + + if (FSGetDeskDir(&otherDesktop, volume.Device()) == B_OK + && otherDesktop.GetEntry(&entry) == B_OK) { + // place desktop items from the mounted volume onto the desktop + Model model(&entry); + if (model.InitCheck() == B_OK) + AddPoses(&model); + } + } + break; + } + } + return _inherited::FSNotification(message); +} + + +void +BFilePanelPoseView::RestoreState(AttributeStreamNode *node) +{ + _inherited::RestoreState(node); + fViewState->SetViewMode(kListMode); +} + + +void +BFilePanelPoseView::RestoreState(const BMessage &message) +{ + _inherited::RestoreState(message); +} + + +void +BFilePanelPoseView::SavePoseLocations(BRect *) +{ +} + + +EntryListBase * +BFilePanelPoseView::InitDirentIterator(const entry_ref *ref) +{ + if (IsDesktopView()) + return DesktopPoseView::InitDesktopDirentIterator(this, ref); + + return _inherited::InitDirentIterator(ref); +} + + +bool +BFilePanelPoseView::ShouldShowPose(const Model *model, const PoseInfo *poseInfo) +{ + if (IsDesktopView() && !ShouldShowDesktopPose(TargetModel()->NodeRef()->device, + model, poseInfo)) + return false; + + return _inherited::ShouldShowPose(model, poseInfo); +} + + +void +BFilePanelPoseView::SetIsDesktop(bool on) +{ + fIsDesktop = on; +} + + +bool +BFilePanelPoseView::IsDesktopView() const +{ + return fIsDesktop; +} + + +void +BFilePanelPoseView::ShowVolumes(bool visible, bool showShared) +{ + if (IsDesktopView()) { + if (!visible) + RemoveRootPoses(); + else + AddRootPoses(true, showShared); + } + + + TFilePanel *filepanel = dynamic_cast(Window()); + if (filepanel) + filepanel->SetTo(TargetModel()->EntryRef()); +} + + +void +BFilePanelPoseView::AdaptToVolumeChange(BMessage *message) +{ + bool showDisksIcon; + bool mountVolumesOnDesktop; + bool mountSharedVolumesOntoDesktop; + + message->FindBool("ShowDisksIcon", &showDisksIcon); + message->FindBool("MountVolumesOntoDesktop", &mountVolumesOnDesktop); + message->FindBool("MountSharedVolumesOntoDesktop", &mountSharedVolumesOntoDesktop); + + BEntry entry("/"); + Model model(&entry); + if (model.InitCheck() == B_OK) { + BMessage monitorMsg; + monitorMsg.what = B_NODE_MONITOR; + + if (showDisksIcon) + monitorMsg.AddInt32("opcode", B_ENTRY_CREATED); + else + monitorMsg.AddInt32("opcode", B_ENTRY_REMOVED); + + monitorMsg.AddInt32("device", model.NodeRef()->device); + monitorMsg.AddInt64("node", model.NodeRef()->node); + monitorMsg.AddInt64("directory", model.EntryRef()->directory); + monitorMsg.AddString("name", model.EntryRef()->name); + TrackerSettings().SetShowDisksIcon(showDisksIcon); + if (Window()) + Window()->PostMessage(&monitorMsg, this); + } + + ShowVolumes(mountVolumesOnDesktop, mountSharedVolumesOntoDesktop); +} + + +void +BFilePanelPoseView::AdaptToDesktopIntegrationChange(BMessage *message) +{ + bool mountVolumesOnDesktop = true; + bool mountSharedVolumesOntoDesktop = true; + + message->FindBool("MountVolumesOntoDesktop", &mountVolumesOnDesktop); + message->FindBool("MountSharedVolumesOntoDesktop", &mountSharedVolumesOntoDesktop); + + ShowVolumes(false, mountSharedVolumesOntoDesktop); + ShowVolumes(mountVolumesOnDesktop, mountSharedVolumesOntoDesktop); +} + diff --git a/src/kits/tracker/FilePanelPriv.h b/src/kits/tracker/FilePanelPriv.h new file mode 100644 index 0000000000..5a427451b9 --- /dev/null +++ b/src/kits/tracker/FilePanelPriv.h @@ -0,0 +1,244 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef _FILE_PANEL_PRIV_H +#define _FILE_PANEL_PRIV_H + +#include + +#include "ContainerWindow.h" +#include "PoseView.h" +#include "TaskLoop.h" + +class BTextControl; +class BFilePanel; +class BRefFilter; +class BMessenger; +class BMenuField; + +namespace BPrivate { + +class BackgroundView; +class BDirMenu; +class AttributeStreamNode; +class BFilePanelPoseView; +class TFavoritesConfigWindow; + +class TFilePanel : public BContainerWindow { +public: + TFilePanel(file_panel_mode = B_OPEN_PANEL, + BMessenger *target = NULL, const BEntry *startDirectory = NULL, + uint32 nodeFlavors = B_FILE_NODE | B_SYMLINK_NODE, + bool multipleSelection = true, BMessage * = NULL, BRefFilter * = NULL, + uint32 containerWindowFlags = 0, + window_look look = B_DOCUMENT_WINDOW_LOOK, + window_feel feel = B_NORMAL_WINDOW_FEEL, + bool hideWhenDone = true); + + virtual ~TFilePanel(); + + BFilePanelPoseView *PoseView() const; + + virtual bool QuitRequested(); + virtual void MenusBeginning(); + virtual void MenusEnded(); + virtual void DispatchMessage(BMessage *message, BHandler *handler); + virtual void ShowContextMenu(BPoint, const entry_ref *, BView *); + + void SetClientObject(BFilePanel *); + void SetRefFilter(BRefFilter *); + void SetSaveText(const char *text); + void SetButtonLabel(file_panel_button, const char *text); + void SetTo(const entry_ref *ref); + virtual void SelectionChanged(); + void HandleOpenButton(); + void HandleSaveButton(); + void Rewind(); + bool IsSavePanel() const; + void Refresh(); + const BMessenger *Target() const; + BRefFilter *Filter() const; + + void SetTarget(BMessenger); + void SetMessage(BMessage *message); + + virtual status_t GetNextEntryRef(entry_ref *); + virtual void MessageReceived(BMessage *); + + void SetHideWhenDone(bool); + bool HidesWhenDone(void); + + bool TrackingMenu() const; + +protected: + BPoseView *NewPoseView(Model *model, BRect rect, uint32 viewMode); + virtual void Init(const BMessage *message = NULL); + virtual void SaveState(bool hide = true); + virtual void SaveState(BMessage &) const; + virtual void RestoreState(); + virtual void RestoreWindowState(AttributeStreamNode *); + virtual void RestoreWindowState(const BMessage &); + virtual void RestoreState(const BMessage &); + + virtual void AddFileContextMenus(BMenu *); + virtual void AddWindowContextMenus(BMenu *); + virtual void AddDropContextMenus(BMenu *); + virtual void AddVolumeContextMenus(BMenu *); + + virtual void SetupNavigationMenu(const entry_ref *, BMenu *); + virtual void OpenDirectory(); + virtual void OpenParent(); + virtual void WindowActivated(bool state); + + static filter_result FSFilter(BMessage *, BHandler **, BMessageFilter *); + static filter_result MessageDropFilter(BMessage *, BHandler **, BMessageFilter *); + int32 ShowCenteredAlert(const char *text, const char *button1, const char *button2 = NULL, + const char *button3 = NULL); + + +private: + bool SwitchDirToDesktopIfNeeded(entry_ref &ref); + bool CanOpenParent() const; + void SwitchDirMenuTo(const entry_ref *ref); + void AdjustButton(); + bool SelectChildInParent(const entry_ref *parent, const node_ref *child); + void OpenSelectionCommon(BMessage *); + + + bool fIsSavePanel; + uint32 fNodeFlavors; + BackgroundView *fBackView; + BDirMenu *fDirMenu; + BMenuField *fDirMenuField; + BTextControl *fTextControl; + BMessenger fTarget; + BFilePanel *fClientObject; + int32 fSelectionIterator; + BMessage *fMessage; + BString fButtonText; + bool fHideWhenDone; + bool fIsTrackingMenu; + TFavoritesConfigWindow *fConfigWindow; + + typedef BContainerWindow _inherited; + +friend class BackgroundView; +}; + + +class BFilePanelPoseView : public BPoseView { +public: + BFilePanelPoseView(Model *, BRect, uint32 resizeMask = B_FOLLOW_ALL); + + virtual bool IsFilePanel() const; + virtual bool FSNotification(const BMessage *); + + void SetIsDesktop(bool); + +protected: + // don't do any volume watching and memtamime watching in file panels for now + virtual void StartWatching(); + virtual void StopWatching(); + + virtual void RestoreState(AttributeStreamNode *); + virtual void RestoreState(const BMessage &); + virtual void SavePoseLocations(BRect * = NULL); + + virtual EntryListBase *InitDirentIterator(const entry_ref *); + virtual bool ShouldShowPose(const Model *, const PoseInfo *); + virtual bool IsDesktopView() const; + + void ShowVolumes(bool visible, bool showShared); + + void AdaptToVolumeChange(BMessage *); + void AdaptToDesktopIntegrationChange(BMessage *); + +private: + bool fIsDesktop; + // this flags makes the distinction between the Desktop as the Root of + // the world and "/boot/home/Desktop" to which we might have navigated + // from the home dir + + typedef BPoseView _inherited; +}; + +// inlines follow + +inline bool +BFilePanelPoseView::IsFilePanel() const +{ + return true; +} + +inline bool +TFilePanel::IsSavePanel() const +{ + return fIsSavePanel; +} + +inline const BMessenger * +TFilePanel::Target() const +{ + return &fTarget; +} + +inline void +TFilePanel::Refresh() +{ + fPoseView->Refresh(); +} + +inline bool +TFilePanel::HidesWhenDone(void) +{ + return fHideWhenDone; +} + +inline void +TFilePanel::SetHideWhenDone(bool on) +{ + fHideWhenDone = on; +} + +inline bool +TFilePanel::TrackingMenu() const +{ + return fIsTrackingMenu; +} + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/FilePermissionsView.cpp b/src/kits/tracker/FilePermissionsView.cpp new file mode 100644 index 0000000000..5444bb29c5 --- /dev/null +++ b/src/kits/tracker/FilePermissionsView.cpp @@ -0,0 +1,347 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include + +#include "FilePermissionsView.h" + +#include + + +const uint32 kPermissionsChanged = 'prch'; +const uint32 kNewOwnerEntered = 'nwow'; +const uint32 kNewGroupEntered = 'nwgr'; + + +FilePermissionsView::FilePermissionsView(BRect rect, Model *model) + : BView(rect, "FilePermissionsView", B_FOLLOW_LEFT_RIGHT, B_WILL_DRAW), + fModel(model) +{ + // Constants for the column labels: "User", "Group" and "Other". + const float kColumnLabelMiddle = 77, kColumnLabelTop = 6, kColumnLabelSpacing = 37, + kColumnLabelBottom = 20, kColumnLabelWidth = 35, kAttribFontHeight = 10; + + BStringView *strView; + + strView = new BStringView(BRect(kColumnLabelMiddle - kColumnLabelWidth / 2, + kColumnLabelTop, kColumnLabelMiddle + kColumnLabelWidth / 2, kColumnLabelBottom), + "", "Owner"); + AddChild(strView); + strView->SetAlignment(B_ALIGN_CENTER); + strView->SetFontSize(kAttribFontHeight); + + strView = new BStringView(BRect(kColumnLabelMiddle - kColumnLabelWidth / 2 + + kColumnLabelSpacing, kColumnLabelTop, kColumnLabelMiddle + kColumnLabelWidth / 2 + + kColumnLabelSpacing, kColumnLabelBottom), "", "Group"); + AddChild(strView); + strView->SetAlignment(B_ALIGN_CENTER); + strView->SetFontSize(kAttribFontHeight); + + strView = new BStringView(BRect(kColumnLabelMiddle - kColumnLabelWidth / 2 + + 2 * kColumnLabelSpacing, kColumnLabelTop, kColumnLabelMiddle + kColumnLabelWidth / 2 + + 2 * kColumnLabelSpacing, kColumnLabelBottom), "", "Other"); + AddChild(strView); + strView->SetAlignment(B_ALIGN_CENTER); + strView->SetFontSize(kAttribFontHeight); + + // Constants for the row labels: "Read", "Write" and "Execute". + const float kRowLabelLeft = 10, kRowLabelTop = kColumnLabelTop + 15, + kRowLabelVerticalSpacing = 18, kRowLabelRight = kColumnLabelMiddle + - kColumnLabelWidth / 2 - 5, kRowLabelHeight = 14; + + strView = new BStringView(BRect(kRowLabelLeft, kRowLabelTop, kRowLabelRight, + kRowLabelTop + kRowLabelHeight), "", "Read"); + AddChild(strView); + strView->SetAlignment(B_ALIGN_RIGHT); + strView->SetFontSize(kAttribFontHeight); + + strView = new BStringView(BRect(kRowLabelLeft, kRowLabelTop + + kRowLabelVerticalSpacing, kRowLabelRight, kRowLabelTop + + kRowLabelVerticalSpacing + kRowLabelHeight), "", "Write"); + AddChild(strView); + strView->SetAlignment(B_ALIGN_RIGHT); + strView->SetFontSize(kAttribFontHeight); + + strView = new BStringView(BRect(kRowLabelLeft, kRowLabelTop + + 2 * kRowLabelVerticalSpacing, kRowLabelRight, kRowLabelTop + + 2 * kRowLabelVerticalSpacing + kRowLabelHeight), "", "Execute"); + AddChild(strView); + strView->SetAlignment(B_ALIGN_RIGHT); + strView->SetFontSize(kAttribFontHeight); + + // Constants for the 3x3 check box array. + const float kLeftMargin = kRowLabelRight + 15, kTopMargin = kRowLabelTop - 2, + kHorizontalSpacing = kColumnLabelSpacing, kVerticalSpacing = kRowLabelVerticalSpacing, + kCheckBoxWidth = 18, kCheckBoxHeight = 18; + + FocusCheckBox **checkBoxArray[3][3] = { + { &fReadUserCheckBox, &fReadGroupCheckBox, &fReadOtherCheckBox }, + { &fWriteUserCheckBox, &fWriteGroupCheckBox, &fWriteOtherCheckBox }, + { &fExecuteUserCheckBox, &fExecuteGroupCheckBox, &fExecuteOtherCheckBox }}; + + for (int32 x = 0; x < 3; x++) { + for (int32 y = 0; y < 3; y++) { + *checkBoxArray[y][x] = + new FocusCheckBox(BRect(kLeftMargin + kHorizontalSpacing * x, + kTopMargin + kVerticalSpacing * y, + kLeftMargin + kHorizontalSpacing * x + kCheckBoxWidth, + kTopMargin + kVerticalSpacing * y + kCheckBoxHeight), + "", "", new BMessage(kPermissionsChanged)); + AddChild(*checkBoxArray[y][x]); + } + } + + const float kTextControlLeft = 170, kTextControlRight = 270, + kTextControlTop = kColumnLabelTop, kTextControlHeight = 14, kTextControlSpacing = 16; + + strView = new BStringView(BRect(kTextControlLeft, kTextControlTop, kTextControlRight, + kTextControlTop + kTextControlHeight), "", "Owner"); + strView->SetAlignment(B_ALIGN_CENTER); + strView->SetFontSize(kAttribFontHeight); + AddChild(strView); + + fOwnerTextControl = new BTextControl(BRect(kTextControlLeft, kTextControlTop - 2 + + kTextControlSpacing, kTextControlRight, kTextControlTop + kTextControlHeight - 2 + + kTextControlSpacing), "", "", "", new BMessage(kNewOwnerEntered)); + fOwnerTextControl->SetDivider(0); + AddChild(fOwnerTextControl); + + strView = new BStringView(BRect(kTextControlLeft, kTextControlTop + 5 + + 2 * kTextControlSpacing, kTextControlRight, kTextControlTop + 2 + + 2 * kTextControlSpacing + kTextControlHeight), "", "Group"); + strView->SetAlignment(B_ALIGN_CENTER); + strView->SetFontSize(kAttribFontHeight); + AddChild(strView); + + fGroupTextControl = new BTextControl(BRect(kTextControlLeft, kTextControlTop + + 3 * kTextControlSpacing, kTextControlRight, kTextControlTop + + 3 * kTextControlSpacing + kTextControlHeight), "", "", "", + new BMessage(kNewGroupEntered)); + fGroupTextControl->SetDivider(0); + AddChild(fGroupTextControl); + + SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + + ModelChanged(model); +} + + +void +FilePermissionsView::ModelChanged(Model *model) +{ + fModel = model; + + bool hideCheckBoxes = false; + uid_t nodeOwner = 0; + gid_t nodeGroup = 0; + mode_t perms = 0; + + if (fModel != NULL) { + BNode node(fModel->EntryRef()); + + if (node.InitCheck() == B_OK) { + if (fReadUserCheckBox->IsHidden()) { + fReadUserCheckBox->Show(); + fReadGroupCheckBox->Show(); + fReadOtherCheckBox->Show(); + fWriteUserCheckBox->Show(); + fWriteGroupCheckBox->Show(); + fWriteOtherCheckBox->Show(); + fExecuteUserCheckBox->Show(); + fExecuteGroupCheckBox->Show(); + fExecuteOtherCheckBox->Show(); + } + + if (node.GetPermissions(&perms) == B_OK) { + fReadUserCheckBox->SetValue((int32)(perms & S_IRUSR)); + fReadGroupCheckBox->SetValue((int32)(perms & S_IRGRP)); + fReadOtherCheckBox->SetValue((int32)(perms & S_IROTH)); + fWriteUserCheckBox->SetValue((int32)(perms & S_IWUSR)); + fWriteGroupCheckBox->SetValue((int32)(perms & S_IWGRP)); + fWriteOtherCheckBox->SetValue((int32)(perms & S_IWOTH)); + fExecuteUserCheckBox->SetValue((int32)(perms & S_IXUSR)); + fExecuteGroupCheckBox->SetValue((int32)(perms & S_IXGRP)); + fExecuteOtherCheckBox->SetValue((int32)(perms & S_IXOTH)); + } else + hideCheckBoxes = true; + + if (node.GetOwner(&nodeOwner) == B_OK) { + BString user; + if (nodeOwner == 0) + if (getenv("USER") != NULL) + user << getenv("USER"); + else + user << "root"; + else + user << nodeOwner; + fOwnerTextControl->SetText(user.String()); + } else + fOwnerTextControl->SetText("Unknown"); + + if (node.GetGroup(&nodeGroup) == B_OK) { + BString group; + if (nodeGroup == 0) + if (getenv("GROUP") != NULL) + group << getenv("GROUP"); + else + group << "0"; + else + group << nodeGroup; + fGroupTextControl->SetText(group.String()); + } else + fGroupTextControl->SetText("Unknown"); + + // Unless we're root, only allow the owner to transfer the ownership, + // i.e. disable text controls if uid:s doesn't match: + thread_id thisThread = find_thread(NULL); + thread_info threadInfo; + get_thread_info(thisThread, &threadInfo); + team_info teamInfo; + get_team_info(threadInfo.team, &teamInfo); + if (teamInfo.uid != 0 && nodeOwner != teamInfo.uid) { + fOwnerTextControl->SetEnabled(false); + fGroupTextControl->SetEnabled(false); + } else { + fOwnerTextControl->SetEnabled(true); + fGroupTextControl->SetEnabled(true); + } + } else + hideCheckBoxes = true; + } else + hideCheckBoxes = true; + + if (hideCheckBoxes) { + fReadUserCheckBox->Hide(); + fReadGroupCheckBox->Hide(); + fReadOtherCheckBox->Hide(); + fWriteUserCheckBox->Hide(); + fWriteGroupCheckBox->Hide(); + fWriteOtherCheckBox->Hide(); + fExecuteUserCheckBox->Hide(); + fExecuteGroupCheckBox->Hide(); + fExecuteOtherCheckBox->Hide(); + } +} + + +void +FilePermissionsView::MessageReceived(BMessage *message) +{ + switch(message->what) { + case kPermissionsChanged: + if (fModel != NULL) { + mode_t newPermissions = 0; + newPermissions = (mode_t)((fReadUserCheckBox->Value() ? S_IRUSR : 0) + | (fReadGroupCheckBox->Value() ? S_IRGRP : 0) + | (fReadOtherCheckBox->Value() ? S_IROTH : 0) + + | (fWriteUserCheckBox->Value() ? S_IWUSR : 0) + | (fWriteGroupCheckBox->Value() ? S_IWGRP : 0) + | (fWriteOtherCheckBox->Value() ? S_IWOTH : 0) + + | (fExecuteUserCheckBox->Value() ? S_IXUSR : 0) + | (fExecuteGroupCheckBox->Value() ? S_IXGRP :0) + | (fExecuteOtherCheckBox->Value() ? S_IXOTH : 0)); + + BNode node(fModel->EntryRef()); + + if (node.InitCheck() == B_OK) + node.SetPermissions(newPermissions); + else { + ModelChanged(fModel); + beep(); + } + } + break; + + case kNewOwnerEntered: + if (fModel != NULL) { + uid_t owner; + if (sscanf(fOwnerTextControl->Text(), "%d", &owner) == 1) { + BNode node(fModel->EntryRef()); + if (node.InitCheck() == B_OK) + node.SetOwner(owner); + else { + ModelChanged(fModel); + beep(); + } + } else { + ModelChanged(fModel); + beep(); + } + } + break; + + case kNewGroupEntered: + if (fModel != NULL) { + gid_t group; + if (sscanf(fGroupTextControl->Text(), "%d", &group) == 1) { + BNode node(fModel->EntryRef()); + if (node.InitCheck() == B_OK) + node.SetGroup(group); + else { + ModelChanged(fModel); + beep(); + } + } else { + ModelChanged(fModel); + beep(); + } + } + break; + + default: + _inherited::MessageReceived(message); + break; + } +} + + +void +FilePermissionsView::AttachedToWindow() +{ + fReadUserCheckBox->SetTarget(this); + fReadGroupCheckBox->SetTarget(this); + fReadOtherCheckBox->SetTarget(this); + fWriteUserCheckBox->SetTarget(this); + fWriteGroupCheckBox->SetTarget(this); + fWriteOtherCheckBox->SetTarget(this); + fExecuteUserCheckBox->SetTarget(this); + fExecuteGroupCheckBox->SetTarget(this); + fExecuteOtherCheckBox->SetTarget(this); + + fOwnerTextControl->SetTarget(this); + fGroupTextControl->SetTarget(this); +} + diff --git a/src/kits/tracker/FilePermissionsView.h b/src/kits/tracker/FilePermissionsView.h new file mode 100644 index 0000000000..a3f00c3542 --- /dev/null +++ b/src/kits/tracker/FilePermissionsView.h @@ -0,0 +1,99 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef FILE_PERMISSIONS_VIEW_H +#define FILE_PERMISSIONS_VIEW_H + +#include +#include + +#include "Model.h" + +namespace BPrivate { + +class FocusCheckBox : public BCheckBox { + public: + FocusCheckBox(BRect rect, const char *name, const char *label, + BMessage *message) + : BCheckBox(rect, name, label, message) + { + } + + virtual void Draw(BRect rect) + { + BCheckBox::Draw(rect); + + if (IsFocus()) { + SetHighColor(0, 0, 255); + StrokeRect(BRect(2 , 4, 12, 14)); + } + } +}; + +class FilePermissionsView : public BView { + public: + FilePermissionsView(BRect, Model *); + + void ModelChanged(Model *); + + protected: + virtual void MessageReceived(BMessage *); + virtual void AttachedToWindow(); + + private: + Model *fModel; + + FocusCheckBox *fReadUserCheckBox; + FocusCheckBox *fReadGroupCheckBox; + FocusCheckBox *fReadOtherCheckBox; + + FocusCheckBox *fWriteUserCheckBox; + FocusCheckBox *fWriteGroupCheckBox; + FocusCheckBox *fWriteOtherCheckBox; + + FocusCheckBox *fExecuteUserCheckBox; + FocusCheckBox *fExecuteGroupCheckBox; + FocusCheckBox *fExecuteOtherCheckBox; + + BTextControl *fOwnerTextControl; + BTextControl *fGroupTextControl; + + typedef BView _inherited; +}; + +} // namespace BPrivate + +using namespace BPrivate; + +#endif /* FILE_PERMISSIONS_VIEW_H */ diff --git a/src/kits/tracker/FindPanel.cpp b/src/kits/tracker/FindPanel.cpp new file mode 100644 index 0000000000..780a58dc5c --- /dev/null +++ b/src/kits/tracker/FindPanel.cpp @@ -0,0 +1,3185 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "Attributes.h" +#include "AutoLock.h" +#include "Commands.h" +#include "ContainerWindow.h" +#include "FindPanel.h" +#include "FSUtils.h" +#include "FunctionObject.h" +#include "IconMenuItem.h" +#include "MimeTypes.h" +#include "MiniMenuField.h" +#include "Tracker.h" +#include "Utilities.h" + +const char *kAllMimeTypes = "mime/ALLTYPES"; + +const BRect kInitialRect(100, 100, 530, 210); +const int32 kInitialAttrModeWindowHeight = 140; +const int32 kIncrementPerAttribute = 30; + +const uint32 kMoreOptionsMessage = 'mrop'; +const uint32 kNameModifiedMessage = 'nmmd'; +const uint32 kSwitchToQueryTemplate = 'swqt'; +const uint32 kRunSaveAsTemplatePanel = 'svtm'; + +const char *kDragNDropTypes [] = { B_QUERY_MIMETYPE, B_QUERY_TEMPLATE_MIMETYPE }; +const char *kDragNDropActionSpecifiers [] = { "Create a Query", "Create a Query template" }; + +const uint32 kAttachFile = 'attf'; + + +namespace BPrivate { + +class MostUsedNames { + public: + MostUsedNames(const char *fileName, const char *directory, int32 maxCount = 5); + ~MostUsedNames(); + + bool ObtainList(BList *list); + void ReleaseList(); + + void AddName(const char *); + + protected: + struct list_entry { + char *name; + int32 count; + }; + + static int CompareNames(const void *a, const void *b); + void LoadList(); + void UpdateList(); + + const char *fFileName; + const char *fDirectory; + bool fLoaded; + mutable Benaphore fLock; + BList fList; + int32 fCount; +}; + +MostUsedNames gMostUsedMimeTypes("MostUsedMimeTypes", "Tracker"); + + +void +MoreOptionsStruct::EndianSwap(void *) +{ + // noop for now +} + + +void +MoreOptionsStruct::SetQueryTemporary(BNode *node, bool on) +{ + MoreOptionsStruct saveMoreOptions; + + ReadAttr(node, kAttrQueryMoreOptions, kAttrQueryMoreOptionsForeign, + B_RAW_TYPE, 0, &saveMoreOptions, sizeof(MoreOptionsStruct), + &MoreOptionsStruct::EndianSwap); + saveMoreOptions.temporary = on; + node->WriteAttr(kAttrQueryMoreOptions, B_RAW_TYPE, 0, &saveMoreOptions, + sizeof(saveMoreOptions)); +} + + +bool +MoreOptionsStruct::QueryTemporary(const BNode *node) +{ + MoreOptionsStruct saveMoreOptions; + + if (ReadAttr(node, kAttrQueryMoreOptions, kAttrQueryMoreOptionsForeign, + B_RAW_TYPE, 0, &saveMoreOptions, sizeof(MoreOptionsStruct), + &MoreOptionsStruct::EndianSwap) == kReadAttrFailed) + return false; + + return saveMoreOptions.temporary; +} + + +// #pragma mark - + + +FindWindow::FindWindow(const entry_ref *newRef, bool editIfTemplateOnly) + : BWindow(kInitialRect, "Find", B_TITLED_WINDOW, B_NOT_RESIZABLE | B_NOT_ZOOMABLE), + fFile(TryOpening(newRef)), + fFromTemplate(false), + fEditTemplateOnly(false), + fSaveAsTemplatePanel(NULL) +{ + if (fFile) { + fRef = *newRef; + if (editIfTemplateOnly) { + char type[B_MIME_TYPE_LENGTH]; + if (BNodeInfo(fFile).GetType(type) == B_OK + && strcasecmp(type, B_QUERY_TEMPLATE_MIMETYPE) == 0) { + fEditTemplateOnly = true; + SetTitle("Edit Query Template"); + } + } + } else { + // no initial query, fall back on the default query template + BEntry entry; + GetDefaultQuery(entry); + entry.GetRef(&fRef); + + if (entry.Exists()) + fFile = TryOpening(&fRef); + else { + // no default query template yet + fFile = new BFile(&entry, O_RDWR | O_CREAT); + if (fFile->InitCheck() < B_OK) { + delete fFile; + fFile = NULL; + } else + SaveQueryAttributes(fFile, true); + } + } + + if (fFile) { + BRect initialRect(FindPanel::InitialViewSize(fFile)); + ResizeTo(initialRect.Width(), initialRect.Height()); + } + + fFromTemplate = IsQueryTemplate(fFile); + + fBackground = new FindPanel(Bounds(), fFile, this, fFromTemplate, fEditTemplateOnly); + AddChild(fBackground); +} + + +FindWindow::~FindWindow() +{ + delete fFile; + delete fSaveAsTemplatePanel; +} + + +BFile * +FindWindow::TryOpening(const entry_ref *ref) +{ + if (!ref) + return NULL; + + BFile *result = new BFile(ref, O_RDWR); + if (result->InitCheck() != B_OK) { + delete result; + result = NULL; + } + return result; +} + + +void +FindWindow::GetDefaultQuery(BEntry &entry) +{ + BPath path; + find_directory(B_USER_DIRECTORY, &path, true); + path.Append("queries"); + mkdir(path.Path(), 0777); + BDirectory directory(path.Path()); + + entry.SetTo(&directory, "default"); +} + + +bool +FindWindow::IsQueryTemplate(BNode *file) +{ + char type[B_MIME_TYPE_LENGTH]; + if (BNodeInfo(file).GetType(type) != B_OK) + return false; + + return strcasecmp(type, B_QUERY_TEMPLATE_MIMETYPE) == 0; +} + + +void +FindWindow::SwitchToTemplate(const entry_ref *ref) +{ + try { + BEntry entry(ref, true); + BFile templateFile(&entry, O_RDONLY); + + ThrowOnInitCheckError(&templateFile); + DisableUpdates(); + // turn off updates to reduce flicker while re-populating the + // window + fBackground->SwitchToTemplate(&templateFile); + EnableUpdates(); + + } catch (...) { + } +} + + +const char * +FindWindow::QueryName() const +{ + if (fFromTemplate) { + if (!fQueryNameFromTemplate.Length()) + fFile->ReadAttrString(kAttrQueryTemplateName, &fQueryNameFromTemplate); + + return fQueryNameFromTemplate.String(); + } + if (!fFile) + return ""; + + return fRef.name; +} + + +static const char * +MakeValidFilename(BString &string) +{ + // make a file name that is legal under bfs and hfs - possibly could + // add code here to accomodate FAT32 etc. too + if (string.Length() > B_FILE_NAME_LENGTH - 1) { + string.Truncate(B_FILE_NAME_LENGTH - 4); + string += B_UTF8_ELLIPSIS; + } + + // replace slashes + int32 length = string.Length(); + char *buf = string.LockBuffer(length); + for (int32 index = length; index-- > 0;) + if (buf[index] == '/' /*|| buf[index] == ':'*/) + buf[index] = '_'; + string.UnlockBuffer(length); + + return string.String(); +} + + +void +FindWindow::GetPredicateString(BString &predicate, bool &dynamicDate) +{ + BQuery query; + BTextControl *textControl = dynamic_cast(FindView("TextControl")); + switch (fBackground->Mode()) { + case kByNameItem: + fBackground->GetByNamePredicate(&query); + query.GetPredicate(&predicate); + break; + + case kByForumlaItem: + predicate.SetTo(textControl->TextView()->Text(), 1023); + break; + + case kByAttributeItem: + fBackground->GetByAttrPredicate(&query, dynamicDate); + query.GetPredicate(&predicate); + break; + } +} + + +void +FindWindow::GetDefaultName(BString &result) +{ + fBackground->GetDefaultName(result); + + time_t timeValue = time(0); + char namebuf[B_FILE_NAME_LENGTH]; + + tm timeData; + localtime_r(&timeValue, &timeData); + + strftime(namebuf, 32, " - %b %d, %I:%M:%S %p", &timeData); + result << namebuf; + + MakeValidFilename(result); +} + + +void +FindWindow::SaveQueryAttributes(BNode *file, bool queryTemplate) +{ + ThrowOnError( BNodeInfo(file).SetType( + queryTemplate ? B_QUERY_TEMPLATE_MIMETYPE : B_QUERY_MIMETYPE) ); + + // save date/time info for recent query support and transient query killer + int32 currentTime = (int32)time(0); + file->WriteAttr(kAttrQueryLastChange, B_INT32_TYPE, 0, ¤tTime, sizeof(int32)); + int32 tmp = 1; + file->WriteAttr("_trk/recentQuery", B_INT32_TYPE, 0, &tmp, sizeof(int32)); +} + + +status_t +FindWindow::SaveQueryAsAttributes(BNode *file, BEntry *entry, bool queryTemplate, + const BMessage *oldAttributes, const BPoint *oldLocation) +{ + if (oldAttributes) + // revive old window settings + BContainerWindow::SetLayoutState(file, oldAttributes); + + if (oldLocation) + // and the file's location + FSSetPoseLocation(entry, *oldLocation); + + BNodeInfo(file).SetType(queryTemplate ? B_QUERY_TEMPLATE_MIMETYPE : B_QUERY_MIMETYPE); + + BString predicate; + bool dynamicDate; + GetPredicateString(predicate, dynamicDate); + file->WriteAttrString(kAttrQueryString, &predicate); + + if (dynamicDate) + file->WriteAttr(kAttrDynamicDateQuery, B_BOOL_TYPE, 0, &dynamicDate, + sizeof(dynamicDate)); + + int32 tmp = 1; + file->WriteAttr("_trk/recentQuery", B_INT32_TYPE, 0, &tmp, sizeof(int32)); + + // write some useful info to help locate the volume to query + BMenuItem *item = fBackground->VolMenu()->FindMarked(); + if (item) { + dev_t dev; + BMessage message; + int32 count = 0; + + int32 itemCount = fBackground->VolMenu()->CountItems(); + for (int32 index = 2; index < itemCount; index++) { + BMenuItem *item = fBackground->VolMenu()->ItemAt(index); + + if (!item->IsMarked()) + continue; + + if (item->Message()->FindInt32("device", &dev) != B_OK) + continue; + + count++; + BVolume volume(dev); + EmbedUniqueVolumeInfo(&message, &volume); + } + + if (count) { + // do we need to embed any volumes + ssize_t size = message.FlattenedSize(); + BString buffer; + status_t result = message.Flatten(buffer.LockBuffer(size), size); + ASSERT(result == B_OK); + result = file->WriteAttr(kAttrQueryVolume, B_MESSAGE_TYPE, 0, + buffer.String(), (size_t)size); + ASSERT(result == size); + buffer.UnlockBuffer(); + } + // default to query for everything + } + + fBackground->SaveWindowState(file, fEditTemplateOnly); + // write out all the dialog items as attributes so that the query can + // be reopened and edited later + + BView *focusedItem = CurrentFocus(); + if (focusedItem) { + // text controls never get the focus, their internal text views do + BView *parent = focusedItem->Parent(); + if (dynamic_cast(parent)) + focusedItem = parent; + + // write out the current focus and, if text control, selection + BString name(focusedItem->Name()); + file->WriteAttrString("_trk/focusedView", &name); + BTextControl *textControl = dynamic_cast(focusedItem); + if (textControl) { + int32 selStart, selEnd; + textControl->TextView()->GetSelection(&selStart, &selEnd); + file->WriteAttr("_trk/focusedSelStart", B_INT32_TYPE, 0, + &selStart, sizeof(selStart)); + file->WriteAttr("_trk/focusedSelEnd", B_INT32_TYPE, 0, + &selEnd, sizeof(selEnd)); + } + } + return B_OK; +} + + +void +FindWindow::Save() +{ + FindSaveCommon(false); + + // close the find panel + PostMessage(B_QUIT_REQUESTED); +} + + +void +FindWindow::Find() +{ + if (!FindSaveCommon(true)) { + // have to wait for the node monitor to force old query to close + // to avoid a race condition + TTracker *tracker = dynamic_cast(be_app); + ASSERT(tracker); + for (int32 timeOut = 0; ; timeOut++) { + if (!tracker->EntryHasWindowOpen(&fRef)) + // window quit, we can post refs received to open a + // new copy + break; + + // PRINT(("waiting for query window to quit, %d\n", timeOut)); + if (timeOut == 5000) { + // the old query window would not quit for some reason + TRESPASS(); + PostMessage(B_QUIT_REQUESTED); + return; + } + snooze(1000); + } + } + + int32 currentTime = (int32)time(0); + fFile->WriteAttr(kAttrQueryLastChange, B_INT32_TYPE, 0, ¤tTime, sizeof(int32)); + + // tell the tracker about it + BMessage message(B_REFS_RECEIVED); + message.AddRef("refs", &fRef); + be_app->PostMessage(&message); + + // close the find panel + PostMessage(B_QUIT_REQUESTED); +} + + +bool +FindWindow::FindSaveCommon(bool find) +{ + // figure out what we need to do + bool readFromOldFile = fFile != NULL; + bool replaceOriginal = fFile && (!fFromTemplate || fEditTemplateOnly); + bool keepPoseLocation = replaceOriginal; + bool newFile = !fFile || (fFromTemplate && !fEditTemplateOnly); + + BEntry entry; + BMessage oldAttributes; + BPoint location; + bool hadLocation = false; + const char *userSpecifiedName = fBackground->UserSpecifiedName(); + + if (readFromOldFile) { + entry.SetTo(&fRef); + BContainerWindow::GetLayoutState(fFile, &oldAttributes); + hadLocation = FSGetPoseLocation(fFile, &location); + } + + if (replaceOriginal) { + fFile->Unset(); + entry.Remove(); + // remove the current entry - need to do this to quit the + // running query and to close the corresponding window + + if (userSpecifiedName && !fEditTemplateOnly) { + // change the name of the old query per users request + fRef.set_name(userSpecifiedName); + entry.SetTo(&fRef); + } + } + + if (newFile) { + // create query file in the user's directory + BPath path; + find_directory(B_USER_DIRECTORY, &path, true); + path.Append("queries"); + // there might be no queries folder yet, create one + mkdir(path.Path(), 0777); + + // either use the user specified name, or go with the name + // generated from the predicate, etc. + if (!userSpecifiedName) { + BString text; + GetDefaultName(text); + path.Append(text.String()); + } else + path.Append(userSpecifiedName); + + entry.SetTo(path.Path()); + entry.Remove(); + entry.GetRef(&fRef); + } + + fFile = new BFile(&entry, O_RDWR | O_CREAT); + ASSERT(fFile->InitCheck() == B_OK); + + SaveQueryAsAttributes(fFile, &entry, !find, newFile ? 0 : &oldAttributes, + (hadLocation && keepPoseLocation) ? &location : 0); + + return newFile; +} + + +void +FindWindow::MessageReceived(BMessage *message) +{ + switch (message->what) { + case kFindButton: + Find(); + break; + + case kSaveButton: + Save(); + break; + + case kAttachFile: + { + entry_ref dir; + const char *name; + bool queryTemplate; + if (message->FindString("name", &name) == B_OK + && message->FindRef("directory", &dir) == B_OK + && message->FindBool("template", &queryTemplate) == B_OK) { + delete fFile; + fFile = NULL; + BDirectory directory(&dir); + BEntry entry(&directory, name); + entry_ref tmpRef; + entry.GetRef(&tmpRef); + fFile = TryOpening(&tmpRef); + if (fFile) { + fRef = tmpRef; + SaveQueryAsAttributes(fFile, &entry, queryTemplate, 0, 0); + // try to save whatever state we aleady have + // to the new query so that if the user + // opens it before runing it from the find panel, + // something reasonable happens + } + } + } + break; + + case kSwitchToQueryTemplate: + { + entry_ref ref; + if (message->FindRef("refs", &ref) == B_OK) + SwitchToTemplate(&ref); + } + break; + + case kRunSaveAsTemplatePanel: + if (fSaveAsTemplatePanel) + fSaveAsTemplatePanel->Show(); + else { + BMessenger panel(BackgroundView()); + fSaveAsTemplatePanel = new BFilePanel(B_SAVE_PANEL, &panel); + fSaveAsTemplatePanel->SetSaveText("Query template"); + fSaveAsTemplatePanel->Window()->SetTitle("Save As Query Template:"); + fSaveAsTemplatePanel->Show(); + } + break; + + default: + _inherited::MessageReceived(message); + break; + } +} + + +// #pragma mark - + +const float kMoreOptionsDelta = 20; + +FindPanel::FindPanel(BRect frame, BFile *node, FindWindow *parent, + bool , bool editTemplateOnly) + : BView(frame, "MainView", B_FOLLOW_ALL, B_WILL_DRAW), + fMode(kByNameItem), + fDraggableIcon(NULL) +{ + SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + + uint32 initialMode = InitialMode(node); + + BRect bounds(Bounds()); + BRect boxBounds(bounds); + + boxBounds.InsetBy(15, 30); + boxBounds.bottom -= 10; + AddChild(new BBox(boxBounds, "Box")); + + BRect rect(boxBounds); + rect.top -= 25; + rect.right = rect.left + 20; + rect.bottom = rect.top + 20; + + BMessenger self(this); + fRecentQueries = new BPopUpMenu("RecentQueries"); + FindPanel::AddRecentQueries(fRecentQueries, true, &self, kSwitchToQueryTemplate); + + AddChild(new MiniMenuField(rect, "RecentQueries", fRecentQueries)); + + rect.left = rect.right + 15; + + // add popup for mime types + fMimeTypeMenu = new BPopUpMenu("MimeTypeMenu"); + fMimeTypeMenu->SetRadioMode(false); + AddMimeTypesToMenu(); + + rect.right = rect.left + 150; + fMimeTypeField = new BMenuField(rect, "MimeTypeMenu", "", fMimeTypeMenu); + fMimeTypeField->SetDivider(0.0f); + fMimeTypeField->MenuItem()->SetLabel("All files and folders"); + AddChild(fMimeTypeField); + + // add popup for search criteria + fSearchModeMenu = new BPopUpMenu("searchMode"); + fSearchModeMenu->AddItem(new BMenuItem("by Name", new BMessage(kByNameItem))); + fSearchModeMenu->AddItem(new BMenuItem("by Attribute", new BMessage(kByAttributeItem))); + fSearchModeMenu->AddItem(new BMenuItem("by Formula", new BMessage(kByForumlaItem))); + + fSearchModeMenu->ItemAt(initialMode == kByNameItem ? 0 : + (initialMode == kByAttributeItem ? 1 : 2))->SetMarked(true); + // mark the appropriate mode + rect.left = rect.right + 10; + rect.right = rect.left + 100; + rect.bottom = rect.top + 15; + BMenuField *menuField = new BMenuField(rect, "", "", fSearchModeMenu); + menuField->SetDivider(0.0f); + AddChild(menuField); + + // add popup for volume list + rect.right = bounds.right - 15; + rect.left = rect.right - 100; + fVolMenu = new BPopUpMenu("", false, false); // don't radioMode + menuField = new BMenuField(rect, "", "On", fVolMenu); + menuField->SetDivider(menuField->StringWidth(menuField->Label()) + 8); + AddChild(menuField); + AddVolumes(fVolMenu); + + if (!editTemplateOnly) { + BPoint draggableIconOrigin(15, bounds.bottom - 35); + BMessage dragNDropMessage(B_SIMPLE_DATA); + dragNDropMessage.AddInt32("be:actions", B_COPY_TARGET); + dragNDropMessage.AddString("be:types", B_FILE_MIME_TYPE); + dragNDropMessage.AddString("be:filetypes", kDragNDropTypes[0]); + dragNDropMessage.AddString("be:filetypes", kDragNDropTypes[1]); + dragNDropMessage.AddString("be:actionspecifier", kDragNDropActionSpecifiers[0]); + dragNDropMessage.AddString("be:actionspecifier", kDragNDropActionSpecifiers[1]); + + BMessenger self(this); + fDraggableIcon = new DraggableQueryIcon(DraggableIcon::PreferredRect(draggableIconOrigin, + B_LARGE_ICON), "saveHere", &dragNDropMessage, + self, B_FOLLOW_LEFT | B_FOLLOW_BOTTOM); + AddChild(fDraggableIcon); + } + + // add the more options collapsible pane + BRect paneInitialRect(bounds); + paneInitialRect.InsetBy(80, 5); + paneInitialRect.right = paneInitialRect.left + 255; + paneInitialRect.top = paneInitialRect.bottom - 30; + BRect paneExpandedRect(paneInitialRect); + paneExpandedRect.bottom += kMoreOptionsDelta; + fMoreOptionsPane = new DialogPane(paneInitialRect, paneExpandedRect, 0, + "moreOptions", B_FOLLOW_LEFT | B_FOLLOW_BOTTOM); + AddChild(fMoreOptionsPane); + + fMoreOptionsPane->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + fMoreOptionsPane->SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + + // set up the contents of the more options pane + BRect expandedBounds(paneExpandedRect); + expandedBounds.OffsetTo(0, 0); + expandedBounds.InsetBy(5, 5); + + rect = expandedBounds; + rect.right = rect.left + 200; + rect.bottom = rect.top + 20;; + fQueryName = new BTextControl(rect, "queryName", "Query name:", "", 0); + fQueryName->SetDivider(fQueryName->StringWidth(fQueryName->Label()) + 5); + fMoreOptionsPane->AddItem(fQueryName, 1); + FillCurrentQueryName(fQueryName, parent); + + rect.top = rect.bottom + 6; + rect.bottom = rect.top + 16; + rect.right = rect.left + 100; + fSearchTrashCheck = new BCheckBox(rect, "searchTrash", "Include trash", 0); + fMoreOptionsPane->AddItem(fSearchTrashCheck, 1); + + rect.OffsetBy(120, 0); + fTemporaryCheck = new BCheckBox(rect, "temporary", "Temporary", 0); + fMoreOptionsPane->AddItem(fTemporaryCheck, 1); + fTemporaryCheck->SetValue(1); + + BRect latchRect(paneInitialRect); + latchRect.left -= 20; + latchRect.right = latchRect.left + 10; + latchRect.top = paneInitialRect.top + paneInitialRect.Height() / 2 - 5; + + latchRect.bottom = latchRect.top + 12; + + fLatch = new PaneSwitch(latchRect, "moreOptionsLatch", true, + B_FOLLOW_BOTTOM | B_FOLLOW_LEFT); + AddChild(fLatch); + fMoreOptionsPane->SetSwitch(fLatch); + + if (initialMode != kByAttributeItem) + AddByNameOrFormulaItems(); + else + AddByAttributeItems(node); + + // add Search button + rect = bounds; + rect.left = rect.right - 80; + rect.top = rect.bottom - 30; + rect.right = rect.left + 60; + rect.bottom = rect.top + 20; + BButton *button; + if (editTemplateOnly) + button = new BButton(rect, "save", "Save", + new BMessage(kSaveButton), B_FOLLOW_RIGHT + B_FOLLOW_BOTTOM); + else + button = new BButton(rect, "find", "Search", + new BMessage(kFindButton), B_FOLLOW_RIGHT + B_FOLLOW_BOTTOM); + + AddChild(button); + button->MakeDefault(true); +} + + +FindPanel::~FindPanel() +{ +} + + +void +FindPanel::AttachedToWindow() +{ + BNode *node = dynamic_cast(Window())->QueryNode(); + fSearchModeMenu->SetTargetForItems(this); + fQueryName->SetTarget(this); + fLatch->SetTarget(fMoreOptionsPane); + RestoreMimeTypeMenuSelection(node); + // preselect the mime we used the last time + // have to do it here because AddByAttributeItems will build different + // menus based on which mime type is preselected + RestoreWindowState(node); + + if (!Window()->CurrentFocus()) { + // try to pick a good focus if we restore to one already + BTextControl *textControl = dynamic_cast(FindView("TextControl")); + if (!textControl) { + // pick the last text control in the attribute view + BString title("TextEntry"); + title << (fAttrViewList.CountItems() - 1); + textControl = dynamic_cast(FindView(title.String())); + } + if (textControl) + textControl->MakeFocus(); + } + + BButton *button = dynamic_cast(FindView("remove")); + if (button) + button->SetTarget(this); + + button = dynamic_cast(FindView("add")); + if (button) + button->SetTarget(this); + + fVolMenu->SetTargetForItems(this); + + // set target for MIME type items + for (int32 index = MimeTypeMenu()->CountItems();index-- > 2;) { + BMenu *submenu = MimeTypeMenu()->ItemAt(index)->Submenu(); + if (submenu != NULL) + submenu->SetTargetForItems(this); + } + fMimeTypeMenu->SetTargetForItems(this); + + + if (fDraggableIcon) + fDraggableIcon->SetTarget(BMessenger(this)); + + fRecentQueries->SetTargetForItems(Window()); +} + +const float kAttrViewDelta = 30; + +BRect +FindPanel::InitialViewSize(const BNode *node) +{ + if (!node || InitialMode(node) != (int32)kByAttributeItem) + return kInitialRect; + + int32 numAttributes = InitialAttrCount(node); + if (numAttributes < 1) + numAttributes = 1; + + BRect result = kInitialRect; + result.bottom = result.top + kInitialAttrModeWindowHeight + + (numAttributes - 1) * kIncrementPerAttribute; + + return result; +} + + +float +FindPanel::ViewHeightForMode(uint32 mode, bool moreOptions) +{ + float result = moreOptions ? kMoreOptionsDelta : 0; + switch (mode) { + case kByForumlaItem: + case kByNameItem: + return 110 + result; + + case kByAttributeItem: + return 110 + result + kAttrViewDelta; + + } + TRESPASS(); + return 0; +} + + +float +FindPanel::BoxHeightForMode(uint32 mode, bool /*moreOptions*/) +{ + switch (mode) { + case kByForumlaItem: + case kByNameItem: + return 40; + + case kByAttributeItem: + return 40 + kAttrViewDelta; + + } + TRESPASS(); + return 0; +} + + +static void +PopUpMenuSetTitle(BMenu *menu, const char *title) +{ + // This should really be in BMenuField + BMenu *bar = menu->Supermenu(); + + ASSERT(bar); + ASSERT(bar->ItemAt(0)); + if (!bar || !bar->ItemAt(0)) + return; + + bar->ItemAt(0)->SetLabel(title); +} + + +void +FindPanel::ShowVolumeMenuLabel() +{ + if (fVolMenu->ItemAt(0)->IsMarked()) { + // "all disks" selected + PopUpMenuSetTitle(fVolMenu, fVolMenu->ItemAt(0)->Label()); + return; + } + + // find out if more than one items are marked + int32 count = fVolMenu->CountItems(); + int32 countSelected = 0; + BMenuItem *tmpItem = NULL; + for (int32 index = 2; index < count; index++) { + BMenuItem *item = fVolMenu->ItemAt(index); + if (item->IsMarked()) { + countSelected++; + tmpItem = item; + } + } + if (countSelected == 0) { + // no disk selected, for now revert to search all disks + // ToDo: + // show no disks here and add a check that will not let the + // query go if the user doesn't pick at least one + fVolMenu->ItemAt(0)->SetMarked(true); + PopUpMenuSetTitle(fVolMenu, fVolMenu->ItemAt(0)->Label()); + } else if (countSelected > 1) + // if more than two disks selected, don't use the disk name + // as a label + PopUpMenuSetTitle(fVolMenu, "multiple disks"); + else { + ASSERT(tmpItem); + PopUpMenuSetTitle(fVolMenu, tmpItem->Label()); + } +} + + +void +FindPanel::MessageReceived(BMessage *message) +{ + entry_ref dir; + const char *name; + + switch (message->what) { + case kVolumeItem: + { + // volume changed + BMenuItem *invokedItem; + dev_t dev; + if (message->FindPointer("source", (void **)&invokedItem) != B_OK) + return; + + if (message->FindInt32("device", &dev) != B_OK) + break; + + BMenu *menu = invokedItem->Menu(); + ASSERT(menu); + + if (dev == -1) { + + // all disks selected, uncheck everything else + int32 count = menu->CountItems(); + for (int32 index = 2; index < count; index++) + menu->ItemAt(index)->SetMarked(false); + + // make all disks the title and check it + PopUpMenuSetTitle(menu, menu->ItemAt(0)->Label()); + menu->ItemAt(0)->SetMarked(true); + + } else { + // a specific volume selected, unmark "all disks" + menu->ItemAt(0)->SetMarked(false); + + // toggle mark on invoked item + int32 count = menu->CountItems(); + for (int32 index = 2; index < count; index++) { + BMenuItem *item = menu->ItemAt(index); + + if (invokedItem == item) { + // we just selected this + bool wasMarked = item->IsMarked(); + item->SetMarked(!wasMarked); + } + } + } + // make sure the right label is showing + ShowVolumeMenuLabel(); + + break; + } + + case kByAttributeItem: + case kByNameItem: + case kByForumlaItem: + SwitchMode(message->what); + break; + + case kAddItem: + AddAttrView(); + break; + + case kRemoveItem: + RemoveAttrView(); + break; + + case kMIMETypeItem: + { + BMenuItem *item; + if (message->FindPointer("source", (void **)&item) == B_OK) { + // don't add the "All files and folders" to the list + if (fMimeTypeMenu->IndexOf(item) != 0) + gMostUsedMimeTypes.AddName(item->Label()); + + SetCurrentMimeType(item); + } + + // mime type switched + if (fMode != kByAttributeItem) + break; + + // the attributes for this type may be different, rip out the existing ones + RemoveAttrViewItems(); + + Window()->ResizeTo(Window()->Frame().Width(), + ViewHeightForMode(kByAttributeItem, fLatch->Value() != 0)); + + BBox *box = dynamic_cast(FindView("Box")); + ASSERT(box); + box->ResizeTo(box->Bounds().Width(), + BoxHeightForMode(kByAttributeItem, fLatch->Value() != 0)); + + AddAttrView(); + break; + } + + case kNameModifiedMessage: + // the query name was edited, make the query permanent + fTemporaryCheck->SetValue(0); + break; + + case B_SAVE_REQUESTED: + { + // finish saving query template from a SaveAs panel + entry_ref ref; + status_t error = message->FindRef("refs", &ref); + + if (error == B_OK) { + // direct entry selected, convert to parent dir and name + BEntry entry(&ref); + error = entry.GetParent(&entry); + if (error == B_OK) { + entry.GetRef(&dir); + name = ref.name; + } + } else { + // parent dir and name selected + error = message->FindRef("directory", &dir); + if (error == B_OK) + error = message->FindString("name", &name); + } + if (error == B_OK) + SaveAsQueryOrTemplate(&dir, name, true); + } + break; + + case B_COPY_TARGET: + { + // finish drag&drop + const char *str; + const char *mimeType = NULL; + const char *actionSpecifier = NULL; + if (message->FindString("be:types", &str) == B_OK + && strcasecmp(str, B_FILE_MIME_TYPE) == 0 + && (message->FindString("be:actionspecifier", &actionSpecifier) == B_OK + || message->FindString("be:filetypes", &mimeType) == B_OK) + && message->FindString("name", &name) == B_OK + && message->FindRef("directory", &dir) == B_OK) { + + bool query = false; + bool queryTemplate = false; + + if (actionSpecifier + && strcasecmp(actionSpecifier, kDragNDropActionSpecifiers[0]) == 0) + query = true; + else if (actionSpecifier + && strcasecmp(actionSpecifier, kDragNDropActionSpecifiers[1]) == 0) + queryTemplate = true; + else if (mimeType && strcasecmp(mimeType, kDragNDropTypes[0]) == 0) + query = true; + else if (mimeType && strcasecmp(mimeType, kDragNDropTypes[1]) == 0) + queryTemplate = true; + + if (query || queryTemplate) + SaveAsQueryOrTemplate(&dir, name, queryTemplate); + } + } + break; + + default: + _inherited::MessageReceived(message); + break; + } +} + + +void +FindPanel::SaveAsQueryOrTemplate(const entry_ref *dir, const char *name, bool queryTemplate) +{ + BDirectory directory(dir); + BFile file(&directory, name, O_RDWR | O_CREAT | O_TRUNC); + BNodeInfo(&file).SetType(queryTemplate ? B_QUERY_TEMPLATE_MIMETYPE : B_QUERY_MIMETYPE); + + BMessage attach(kAttachFile); + attach.AddRef("directory", dir); + attach.AddString("name", name); + attach.AddBool("template", queryTemplate); + Window()->PostMessage(&attach, 0); +} + + +void +FindPanel::BuildAttrQuery(BQuery *query, bool &dynamicDate) const +{ + dynamicDate = false; + + // go through each attrview and add the attr and comparison info + for (int32 index = 0; index < fAttrViewList.CountItems(); index++) { + + TAttrView *view = fAttrViewList.ItemAt(index); + BString title; + title << "TextEntry" << index; + + BTextControl *textControl = dynamic_cast + (view->FindView(title.String())); + if (!textControl) + return; + + BMenuField *menuField = dynamic_cast(view->FindView("MenuField")); + if (!menuField) + return; + + BMenuItem *item = menuField->Menu()->FindMarked(); + if (!item) + continue; + + BMessage *message = item->Message(); + int32 type; + if (message->FindInt32("type", &type) == B_OK) { + + const char *str; + if (message->FindString("name", &str) == B_OK) + query->PushAttr(str); + else + query->PushAttr(item->Label()); + + switch (type) { + case B_STRING_TYPE: + query->PushString(textControl->TextView()->Text(), true); + break; + + case B_TIME_TYPE: + { + int flags = 0; + DEBUG_ONLY(time_t result =) + parsedate_etc(textControl->TextView()->Text(), -1, &flags); + dynamicDate = (flags & PARSEDATE_RELATIVE_TIME) != 0; + PRINT(("parsedate_etc - date is %srelative, %l\n", + dynamicDate ? "" : "not ", result)); + + query->PushDate(textControl->TextView()->Text()); + } + break; + + case B_BOOL_TYPE: + { + uint32 value; + if (strcasecmp(textControl->TextView()->Text(), "true") == 0) + value = 1; + else if (strcasecmp(textControl->TextView()->Text(), "true") == 0) + value = 1; + else + value = (uint32)atoi(textControl->TextView()->Text()); + + value %= 2; + query->PushUInt32(value); + } + break; + + case B_UINT8_TYPE: + case B_UINT16_TYPE: + case B_UINT32_TYPE: + query->PushUInt32((uint32)StringToScalar(textControl->TextView()->Text())); + break; + + case B_INT8_TYPE: + case B_INT16_TYPE: + case B_INT32_TYPE: + query->PushInt32((int32)StringToScalar(textControl->TextView()->Text())); + break; + + case B_UINT64_TYPE: + query->PushUInt64((uint64)StringToScalar(textControl->TextView()->Text())); + break; + + case B_OFF_T_TYPE: + case B_INT64_TYPE: + query->PushInt64(StringToScalar(textControl->TextView()->Text())); + break; + + case B_FLOAT_TYPE: + { + float floatVal; + sscanf(textControl->TextView()->Text(), "%f", &floatVal); + query->PushFloat(floatVal); + } + break; + + case B_DOUBLE_TYPE: + { + double doubleVal; + sscanf(textControl->TextView()->Text(), "%lf", &doubleVal); + query->PushDouble(doubleVal); + } + break; + + } + } + + query_op theOperator; + BMenuItem *operatorItem = item->Submenu()->FindMarked(); + if (operatorItem && operatorItem->Message() != NULL) { + operatorItem->Message()->FindInt32("operator", (int32 *)&theOperator); + query->PushOp(theOperator); + } else + query->PushOp(B_EQ); + + // add logic based on selection in Logic menufield + if (index > 0) { + TAttrView *prevView = fAttrViewList.ItemAt(index - 1); + menuField = dynamic_cast(prevView->FindView("Logic")); + if (menuField) { + item = menuField->Menu()->FindMarked(); + if (item) { + message = item->Message(); + message->FindInt32("combine", (int32 *)&theOperator); + query->PushOp(theOperator); + } + } else + query->PushOp(B_AND); + } + } +} + + +void +FindPanel::PushMimeType(BQuery *query) const +{ + const char *type; + if (CurrentMimeType(&type) == NULL) + return; + + if (strcmp(kAllMimeTypes, type)) { + // add an asterisk if we are searching for a supertype + char buffer[B_FILE_NAME_LENGTH]; + if (strchr(type,'/') == NULL) { + strcpy(buffer,type); + strcat(buffer,"/*"); + type = buffer; + } + + query->PushAttr(kAttrMIMEType); + query->PushString(type); + query->PushOp(B_EQ); + query->PushOp(B_AND); + } +} + + +void +FindPanel::GetByAttrPredicate(BQuery *query, bool &dynamicDate) const +{ + ASSERT(Mode() == (int32)kByAttributeItem); + BuildAttrQuery(query, dynamicDate); + PushMimeType(query); +} + + +void +FindPanel::GetDefaultName(BString &result) const +{ + BTextControl *textControl = dynamic_cast(FindView("TextControl")); + switch (Mode()) { + case kByNameItem: + result << "Name = " << textControl->TextView()->Text(); + break; + + case kByForumlaItem: + result << "Formula " << textControl->TextView()->Text(); + break; + + case kByAttributeItem: + { + BMenuItem *item = fMimeTypeMenu->FindMarked(); + if (item != NULL) + result << item->Label() << ": "; + + for (int32 i = 0; i < fAttrViewList.CountItems(); i++) { + fAttrViewList.ItemAt(i)->GetDefaultName(result); + if (i + 1 < fAttrViewList.CountItems()) + result << ", "; + } + break; + } + } +} + + +const char * +FindPanel::UserSpecifiedName() const +{ + if (fQueryName->Text()[0] == '\0') + return NULL; + + return fQueryName->Text(); +} + + +void +FindPanel::GetByNamePredicate(BQuery *query) const +{ + ASSERT(Mode() == (int32)kByNameItem); + BTextControl *textControl = dynamic_cast(FindView("TextControl")); + ASSERT(textControl); + + query->PushAttr("name"); + query->PushString(textControl->TextView()->Text(), true); + + if (strstr(textControl->TextView()->Text(), "*")) + // assume pattern is a regular expression and try doing an exact match + query->PushOp(B_EQ); + else + query->PushOp(B_CONTAINS); + + PushMimeType(query); +} + + +void +FindPanel::SwitchMode(uint32 mode) +{ + if (fMode == mode) + // no work, bail + return; + + BBox *box = dynamic_cast(FindView("Box")); + ASSERT(box); + + uint32 oldMode = fMode; + BString buffer; + + switch (mode) { + case kByForumlaItem: + if (oldMode == kByAttributeItem || oldMode == kByNameItem) { + BQuery query; + if (oldMode == kByAttributeItem) { + bool dummy; + GetByAttrPredicate(&query, dummy); + } else + GetByNamePredicate(&query); + + query.GetPredicate(&buffer); + } + // fall thru + + case kByNameItem: + { + fMode = mode; + Window()->ResizeTo(Window()->Frame().Width(), + ViewHeightForMode(mode, fLatch->Value() != 0)); + BRect bounds(Bounds()); + bounds.InsetBy(15, 30); + bounds.bottom -= 10; + if (fLatch->Value()) + bounds.bottom -= kMoreOptionsDelta; + box->ResizeTo(bounds.Width(), BoxHeightForMode(mode, fLatch->Value() != 0)); + + RemoveByAttributeItems(); + ShowOrHideMimeTypeMenu(); + AddByNameOrFormulaItems(); + + if (buffer.Length()) { + ASSERT(mode == kByForumlaItem || oldMode == kByAttributeItem); + BTextControl *textControl = dynamic_cast + (FindView("TextControl")); + textControl->SetText(buffer.String()); + } + break; + } + + case kByAttributeItem: + { + fMode = mode; + box->ResizeTo(box->Bounds().Width(), + BoxHeightForMode(mode, fLatch->Value() != 0)); + + Window()->ResizeTo(Window()->Frame().Width(), + ViewHeightForMode(mode, fLatch->Value() != 0)); + + BTextControl *textControl = dynamic_cast + (FindView("TextControl")); + + if (textControl) { + textControl->RemoveSelf(); + delete textControl; + } + + ShowOrHideMimeTypeMenu(); + AddAttrView(); + break; + } + } +} + + +BMenuItem * +FindPanel::CurrentMimeType(const char **type) const +{ + // search for marked item in the list + BMenuItem *item = MimeTypeMenu()->FindMarked(); + + // if it's one of the most used items, ignore it + if (item != NULL && MimeTypeMenu()->IndexOf(item) != 0 && item->Submenu() == NULL) + item = NULL; + + if (item == NULL) { + for (int32 index = MimeTypeMenu()->CountItems(); index-- > 0;) { + BMenu *submenu = MimeTypeMenu()->ItemAt(index)->Submenu(); + if (submenu != NULL && (item = submenu->FindMarked()) != NULL) + break; + } + } + + if (type && item != NULL) { + BMessage *message = item->Message(); + if (!message) + return NULL; + + if (message->FindString("mimetype", type) != B_OK) + return NULL; + } + return item; +} + + +status_t +FindPanel::SetCurrentMimeType(BMenuItem *item) +{ + // unmark old MIME type (in most used list, and the tree) + + BMenuItem *marked = CurrentMimeType(); + if (marked != NULL) { + marked->SetMarked(false); + + if ((marked = MimeTypeMenu()->FindMarked()) != NULL) + marked->SetMarked(false); + } + + // mark new MIME type (in most used list, and the tree) + + if (item != NULL) { + item->SetMarked(true); + fMimeTypeField->MenuItem()->SetLabel(item->Label()); + + BMenuItem *search; + for (int32 i = 2;(search = MimeTypeMenu()->ItemAt(i)) != NULL;i++) { + if (item == search || !search->Label()) + continue; + if (!strcmp(item->Label(),search->Label())) { + search->SetMarked(true); + break; + } + BMenu *submenu = search->Submenu(); + if (submenu) { + for (int32 j = submenu->CountItems();j-- > 0;) { + BMenuItem *sub = submenu->ItemAt(j); + if (!strcmp(item->Label(),sub->Label())) { + sub->SetMarked(true); + break; + } + } + } + } + } + return B_OK; +} + + +status_t +FindPanel::SetCurrentMimeType(const char *label) +{ + // unmark old MIME type (in most used list, and the tree) + + BMenuItem *marked = CurrentMimeType(); + if (marked != NULL) { + marked->SetMarked(false); + + if ((marked = MimeTypeMenu()->FindMarked()) != NULL) + marked->SetMarked(false); + } + + // mark new MIME type (in most used list, and the tree) + + fMimeTypeField->MenuItem()->SetLabel(label); + bool found = false; + + for (int32 index = MimeTypeMenu()->CountItems(); index-- > 0;) { + BMenuItem *item = MimeTypeMenu()->ItemAt(index); + BMenu *submenu = item->Submenu(); + if (submenu != NULL && !found) { + for (int32 subIndex = submenu->CountItems(); subIndex-- > 0;) { + BMenuItem *subItem = submenu->ItemAt(subIndex); + if (strcmp(label, subItem->Label()) == 0) { + subItem->SetMarked(true); + found = true; + } + } + } + if (strcmp(label, item->Label()) == 0) { + item->SetMarked(true); + return B_OK; + } + } + + return found ? B_OK : B_ENTRY_NOT_FOUND; +} + + +bool +FindPanel::AddOneMimeTypeToMenu(const ShortMimeInfo *info, void *castToMenu) +{ + BPopUpMenu *menu = static_cast(castToMenu); + + BMimeType type(info->InternalName()); + BMimeType super; + type.GetSupertype(&super); + if (super.InitCheck() < B_OK) + return false; + + BMenuItem *superItem = menu->FindItem(super.Type()); + if (superItem != NULL) { + BMessage *msg = new BMessage(kMIMETypeItem); + msg->AddString("mimetype", info->InternalName()); + + superItem->Submenu()->AddItem(new BMenuItem(info->ShortDescription(), msg)); + } + + return false; +} + + +void +FindPanel::AddMimeTypesToMenu() +{ + BMessage *itemMessage = new BMessage(kMIMETypeItem); + itemMessage->AddString("mimetype", kAllMimeTypes); + MimeTypeMenu()->AddItem(new BMenuItem("All files and folders", itemMessage)); + MimeTypeMenu()->AddSeparatorItem(); + MimeTypeMenu()->ItemAt(0)->SetMarked(true); + + // add recent MIME types + + TTracker *tracker = dynamic_cast(be_app); + + BList list; + if (gMostUsedMimeTypes.ObtainList(&list) && tracker) { + int32 count = 0; + for (int32 index = 0; index < list.CountItems(); index++) { + const char *name = (const char *)list.ItemAt(index); + + const ShortMimeInfo *info; + if ((info = tracker->MimeTypes()->FindMimeType(name)) == NULL) + continue; + + BMessage *message = new BMessage(kMIMETypeItem); + message->AddString("mimetype", info->InternalName()); + + MimeTypeMenu()->AddItem(new BMenuItem(name, message)); + count++; + } + if (count != 0) + MimeTypeMenu()->AddSeparatorItem(); + + gMostUsedMimeTypes.ReleaseList(); + } + + // add MIME type tree list + + BMessage types; + if (BMimeType::GetInstalledSupertypes(&types) == B_OK) { + const char *superType; + int32 index = 0; + + while (types.FindString("super_types",index++,&superType) == B_OK) { + BMenu *superMenu = new BMenu(superType); + + BMessage *message = new BMessage(kMIMETypeItem); + message->AddString("mimetype",superType); + + MimeTypeMenu()->AddItem(new BMenuItem(superMenu,message)); + + // the MimeTypeMenu's font is not correct at this time + superMenu->SetFont(be_plain_font); + } + } + + if (tracker) + tracker->MimeTypes()->EachCommonType(&FindPanel::AddOneMimeTypeToMenu, + MimeTypeMenu()); + + // remove empty super type menus (and set target) + + for (int32 index = MimeTypeMenu()->CountItems();index-- > 2;) { + BMenuItem *item = MimeTypeMenu()->ItemAt(index); + BMenu *submenu = item->Submenu(); + if (submenu != NULL) { + if (submenu->CountItems() == 0) { + MimeTypeMenu()->RemoveItem(item); + delete item; + } else + submenu->SetTargetForItems(this); + } + } + + MimeTypeMenu()->SetTargetForItems(this); +} + + +void +FindPanel::AddVolumes(BMenu *menu) +{ +// ToDo: add calls to this to rebuild the menu when a volume gets mounted + + BMessage *message = new BMessage(kVolumeItem); + message->AddInt32("device", -1); + menu->AddItem(new BMenuItem("All disks", message)); + menu->AddSeparatorItem(); + PopUpMenuSetTitle(menu, "All disks"); + + BVolumeRoster roster; + BVolume volume; + roster.Rewind(); + while (roster.GetNextVolume(&volume) == B_OK) { + if (volume.IsPersistent() && volume.KnowsQuery()) { + BDirectory root; + if (volume.GetRootDirectory(&root) != B_OK) + continue; + + BEntry entry; + root.GetEntry(&entry); + + Model model(&entry, true); + if (model.InitCheck() != B_OK) + continue; + + message = new BMessage(kVolumeItem); + message->AddInt32("device", volume.Device()); + menu->AddItem(new ModelMenuItem(&model, model.Name(), message)); + } + } + + if (menu->ItemAt(0)) + menu->ItemAt(0)->SetMarked(true); + + menu->SetTargetForItems(this); +} + + +typedef std::pair EntryWithDate; + +static int +SortByDatePredicate(const EntryWithDate *entry1, const EntryWithDate *entry2) +{ + return entry1->second > entry2->second ? + -1 : (entry1->second == entry2->second ? 0 : 1); +} + +struct AddOneRecentParams { + BMenu *menu; + const BMessenger *target; + uint32 what; +}; + +static const entry_ref * +AddOneRecentItem(const entry_ref *ref, void *castToParams) +{ + AddOneRecentParams *params = (AddOneRecentParams *)castToParams; + + BMessage *message = new BMessage(params->what); + message->AddRef("refs", ref); + + char type[B_MIME_TYPE_LENGTH]; + BNode node(ref); + BNodeInfo(&node).GetType(type); + BMenuItem *item = new IconMenuItem(ref->name, message, type, B_MINI_ICON); + item->SetTarget(*params->target); + params->menu->AddItem(item); + + return NULL; +} + + +void +FindPanel::AddRecentQueries(BMenu *menu, bool addSaveAsItem, const BMessenger *target, + uint32 what) +{ + BObjectList templates(10, true); + BObjectList recentQueries(10, true); + + // find all the queries on all volumes + BVolumeRoster roster; + BVolume volume; + roster.Rewind(); + while (roster.GetNextVolume(&volume) == B_OK) { + if (volume.IsPersistent() && volume.KnowsQuery() && volume.KnowsAttr()) { + + BQuery query; + query.SetVolume(&volume); + query.SetPredicate("_trk/recentQuery == 1"); + if (query.Fetch() != B_OK) + continue; + + entry_ref ref; + while (query.GetNextRef(&ref) == B_OK) { + // ignore queries in the Trash + if (FSInTrashDir(&ref)) + continue; + + char type[B_MIME_TYPE_LENGTH]; + BNode node(&ref); + BNodeInfo(&node).GetType(type); + + if (strcasecmp(type, B_QUERY_TEMPLATE_MIMETYPE) == 0) + templates.AddItem(new entry_ref(ref)); + else { + uint32 changeTime; + if (node.ReadAttr(kAttrQueryLastChange, B_INT32_TYPE, 0, + &changeTime, sizeof(uint32)) != sizeof(uint32)) + continue; + + recentQueries.AddItem(new EntryWithDate(ref, changeTime)); + } + + } + } + } + + // we are only adding last ten queries + recentQueries.SortItems(SortByDatePredicate); + + // but all templates + AddOneRecentParams params; + params.menu = menu; + params.target = target; + params.what = what; + templates.EachElement(AddOneRecentItem, ¶ms); + + int32 count = recentQueries.CountItems(); + // show only up to 10 recent queries + if (count > 10) + count = 10; + + if (templates.CountItems() && count) + menu->AddSeparatorItem(); + + for (int32 index = 0; index < count; index++) + AddOneRecentItem(&recentQueries.ItemAt(index)->first, ¶ms); + + + if (addSaveAsItem) { + // add a Save as template item + if (count || templates.CountItems()) + menu->AddSeparatorItem(); + + BMessage *message = new BMessage(kRunSaveAsTemplatePanel); + BMenuItem *item = new BMenuItem("Save Query as template"B_UTF8_ELLIPSIS, message); + menu->AddItem(item); + } +} + + +void +FindPanel::AddOneAttributeItem(BBox *box, BRect rect) +{ + TAttrView *attrView = new TAttrView(rect, fAttrViewList.CountItems()); + fAttrViewList.AddItem(attrView); + + box->AddChild(attrView); + attrView->MakeTextViewFocus(); +} + + +void +FindPanel::SetUpAddRemoveButtons(BBox *box) +{ + BButton *button = dynamic_cast(Window()->FindView("remove")); + if (!button) { + BRect rect = box->Bounds(); + rect.InsetBy(5, 10); + rect.top = rect.bottom - 20; + rect.right = rect.left + 40; + + button = new BButton(rect, "add", "Add", new BMessage(kAddItem), + B_FOLLOW_RIGHT + B_FOLLOW_BOTTOM); + button->SetTarget(this); + box->AddChild(button); + + rect.OffsetBy(50, 0); + rect.right = rect.left + 55; + button = new BButton(rect, "remove", "Remove", new BMessage(kRemoveItem), + B_FOLLOW_RIGHT + B_FOLLOW_BOTTOM); + + button->SetEnabled(false); + button->SetTarget(this); + box->AddChild(button); + } + // enable remove button as needed + button->SetEnabled(fAttrViewList.CountItems() > 1); +} + + +void +FindPanel::FillCurrentQueryName(BTextControl *queryName, FindWindow *window) +{ + ASSERT(window); + queryName->SetText(window->QueryName()); +} + + +void +FindPanel::AddAttrView() +{ + BBox *box = dynamic_cast(FindView("Box")); + BRect bounds(Bounds()); + + TAttrView *previous = fAttrViewList.LastItem(); + + if (previous) + Window()->ResizeBy(0, 30); + + bounds = Bounds(); + bounds.InsetBy(15, 30); + bounds.bottom -= 10 + (fLatch->Value() ? kAttrViewDelta : 0); + + if (previous) { + box->ResizeTo(bounds.Width(), bounds.Height()); + bounds = previous->Frame(); + bounds.OffsetBy(0, 30); + } else { + bounds = box->Bounds(); + bounds.InsetBy(5, 5); + bounds.bottom = bounds.top + 25; + } + AddOneAttributeItem(box, bounds); + + // add logic to previous attrview + if (previous) + previous->AddLogicMenu(); + + SetUpAddRemoveButtons(box); + + // populate mime popup + TAttrView *last = fAttrViewList.LastItem(); + last->AddMimeTypeAttrs(); +} + + +void +FindPanel::RemoveAttrView() +{ + if (fAttrViewList.CountItems() < 2) + return; + + BBox *box = dynamic_cast(FindView("Box")); + TAttrView *attrView = fAttrViewList.LastItem(); + if (!box || !attrView) + return; + + Window()->ResizeBy(0, -30); + BRect bounds(Bounds()); + bounds.InsetBy(15, 30); + bounds.bottom -= 10 + (fLatch->Value() ? kAttrViewDelta : 0); + box->ResizeTo(bounds.Width(), bounds.Height()); + + fAttrViewList.RemoveItem(attrView); + attrView->RemoveSelf(); + delete attrView; + + attrView = fAttrViewList.LastItem(); + attrView->RemoveLogicMenu(); + attrView->MakeTextViewFocus(); + + if (fAttrViewList.CountItems() != 1) + return; + + BButton *button = dynamic_cast(Window()->FindView("remove")); + if (button) + button->SetEnabled(false); +} + + +uint32 +FindPanel::InitialMode(const BNode *node) +{ + if (!node || node->InitCheck() != B_OK) + return kByNameItem; + + uint32 result; + if (node->ReadAttr(kAttrQueryInitialMode, B_INT32_TYPE, 0, + (int32 *)&result, sizeof(int32)) <= 0) + return kByNameItem; + + return result; +} + + +int32 +FindPanel::InitialAttrCount(const BNode *node) +{ + if (!node || node->InitCheck() != B_OK) + return 1; + + int32 result; + if (node->ReadAttr(kAttrQueryInitialNumAttrs, B_INT32_TYPE, 0, + &result, sizeof(int32)) <= 0) + return 1; + + return result; +} + + +static int32 +SelectItemWithLabel(BMenu *menu, const char *label) +{ + for (int32 index = menu->CountItems(); index-- > 0;) { + BMenuItem *item = menu->ItemAt(index); + + if (strcmp(label, item->Label()) == 0) { + item->SetMarked(true); + return index; + } + } + return -1; +} + + +void +FindPanel::SaveWindowState(BNode *node, bool editTemplate) +{ + ASSERT(node->InitCheck() == B_OK); + + BMenuItem *item = CurrentMimeType(); + if (item) { + BString label(item->Label()); + node->WriteAttrString(kAttrQueryInitialMime, &label); + } + + uint32 mode = Mode(); + node->WriteAttr(kAttrQueryInitialMode, B_INT32_TYPE, 0, + (int32 *)&mode, sizeof(int32)); + + MoreOptionsStruct saveMoreOptions; + saveMoreOptions.showMoreOptions = fLatch->Value() != 0; + + saveMoreOptions.searchTrash = fSearchTrashCheck->Value() != 0; + saveMoreOptions.temporary = fTemporaryCheck->Value() != 0; + + if (node->WriteAttr(kAttrQueryMoreOptions, B_RAW_TYPE, 0, &saveMoreOptions, + sizeof(saveMoreOptions)) == sizeof(saveMoreOptions)) + node->RemoveAttr(kAttrQueryMoreOptionsForeign); + + if (editTemplate) { + if (UserSpecifiedName()) { + BString name(UserSpecifiedName()); + node->WriteAttrString(kAttrQueryTemplateName, &name); + } + } + + switch (Mode()) { + case kByAttributeItem: + { + BMessage message; + int32 count = fAttrViewList.CountItems(); + node->WriteAttr(kAttrQueryInitialNumAttrs, B_INT32_TYPE, 0, + &count, sizeof(int32)); + + for (int32 index = 0; index < count; index++) + fAttrViewList.ItemAt(index)->SaveState(&message, index); + + ssize_t size = message.FlattenedSize(); + char *buffer = new char[size]; + status_t result = message.Flatten(buffer, size); + ASSERT(result == B_OK); + result = node->WriteAttr(kAttrQueryInitialAttrs, B_MESSAGE_TYPE, 0, + buffer, (size_t)size); + ASSERT(result == size); + delete [] buffer; + } + break; + + case kByNameItem: + case kByForumlaItem: + { + BTextControl *textControl = dynamic_cast + (FindView("TextControl")); + ASSERT(textControl); + BString formula(textControl->TextView()->Text()); + node->WriteAttrString(kAttrQueryInitialString, &formula); + break; + } + } +} + + +void +FindPanel::SwitchToTemplate(const BNode *node) +{ + if (fLatch->Value()) { + // this is kind of a hack - the following code up to + // RestoreWindowState assumes the latch is closed + // Would be nicer if all the size of the window were set once + // and correctly - this is not easy thought because the latch + // controls the window size in relative increments + fLatch->SetValue(0); + fMoreOptionsPane->SetMode(0); + } + + SwitchMode(InitialMode(node)); + // update the menu to correspond to the mode + MarkNamedMenuItem(fSearchModeMenu, InitialMode(node), true); + + BRect initialRect(InitialViewSize(node)); + Window()->ResizeTo(initialRect.Width(), initialRect.Height()); + if (Mode() == (int32)kByAttributeItem) { + RemoveByAttributeItems(); + ResizeAttributeBox(node); + AddByAttributeItems(node); + } + + RestoreWindowState(node); +} + + +void +FindPanel::RestoreMimeTypeMenuSelection(const BNode *node) +{ + if (Mode() == (int32)kByForumlaItem || node == NULL || node->InitCheck() != B_OK) + return; + + BString buffer; + if (node->ReadAttrString(kAttrQueryInitialMime, &buffer) == B_OK) + SetCurrentMimeType(buffer.String()); +} + + +void +FindPanel::RestoreWindowState(const BNode *node) +{ + fMode = InitialMode(node); + if (!node || node->InitCheck() != B_OK) + return; + + ShowOrHideMimeTypeMenu(); + RestoreMimeTypeMenuSelection(node); + MoreOptionsStruct saveMoreOptions; + + bool storesMoreOptions = ReadAttr(node, kAttrQueryMoreOptions, + kAttrQueryMoreOptionsForeign, B_RAW_TYPE, 0, &saveMoreOptions, + sizeof(saveMoreOptions), &MoreOptionsStruct::EndianSwap) + != kReadAttrFailed; + + if (storesMoreOptions) { + // need to sanitize to true or false here, could have picked + // up garbage from attributes + + saveMoreOptions.showMoreOptions = + (saveMoreOptions.showMoreOptions != 0); + + fLatch->SetValue(saveMoreOptions.showMoreOptions); + fMoreOptionsPane->SetMode(saveMoreOptions.showMoreOptions); + + fSearchTrashCheck->SetValue(saveMoreOptions.searchTrash); + fTemporaryCheck->SetValue(saveMoreOptions.temporary); + + fQueryName->SetModificationMessage(NULL); + FillCurrentQueryName(fQueryName, dynamic_cast(Window())); + + // set modification message after checking the temporary check box, + // and filling out the text control so that we do not + // always trigger clearing of the temporary check box. + fQueryName->SetModificationMessage(new BMessage(kNameModifiedMessage)); + } + + // get volumes to perform query on + bool searchAllVolumes = true; + + attr_info info; + if (node->GetAttrInfo(kAttrQueryVolume, &info) == B_OK) { + char *buffer = new char[info.size]; + if (node->ReadAttr(kAttrQueryVolume, B_MESSAGE_TYPE, 0, buffer, (size_t)info.size) + == info.size) { + BMessage message; + if (message.Unflatten(buffer) == B_OK) { + for (int32 index = 0; ;index++) { + ASSERT(index < 100); + BVolume volume; + // match a volume with the info embedded in the message + status_t result = MatchArchivedVolume(&volume, &message, index); + if (result == B_OK) { + char name[256]; + volume.GetName(name); + SelectItemWithLabel(fVolMenu, name); + searchAllVolumes = false; + } else if (result != B_DEV_BAD_DRIVE_NUM) + // if B_DEV_BAD_DRIVE_NUM, the volume just isn't mounted this + // time around, keep looking for more + // if other error, bail + break; + } + } + } + delete [] buffer; + } + // mark or unmark "All disks" + fVolMenu->ItemAt(0)->SetMarked(searchAllVolumes); + ShowVolumeMenuLabel(); + + switch (Mode()) { + case kByAttributeItem: + { + int32 count = InitialAttrCount(node); + + attr_info info; + if (node->GetAttrInfo(kAttrQueryInitialAttrs, &info) != B_OK) + break; + char *buffer = new char[info.size]; + if (node->ReadAttr(kAttrQueryInitialAttrs, B_MESSAGE_TYPE, 0, buffer, (size_t)info.size) + == info.size) { + BMessage message; + if (message.Unflatten(buffer) == B_OK) + for (int32 index = 0; index < count; index++) + fAttrViewList.ItemAt(index)->RestoreState(message, index); + } + delete [] buffer; + break; + } + + case kByNameItem: + case kByForumlaItem: + { + BString buffer; + if (node->ReadAttrString(kAttrQueryInitialString, &buffer) == B_OK) { + BTextControl *textControl = dynamic_cast + (FindView("TextControl")); + ASSERT(textControl); + + textControl->TextView()->SetText(buffer.String()); + } + } + break; + } + + // try to restore focus and possibly text selection + BString focusedView; + if (node->ReadAttrString("_trk/focusedView", &focusedView) == B_OK) { + BView *view = FindView(focusedView.String()); + if (view) { + view->MakeFocus(); + BTextControl *textControl = dynamic_cast(view); + if (textControl) { + int32 selStart = 0, selEnd = LONG_MAX; + node->ReadAttr("_trk/focusedSelStart", B_INT32_TYPE, 0, + &selStart, sizeof(selStart)); + node->ReadAttr("_trk/focusedSelEnd", B_INT32_TYPE, 0, + &selEnd, sizeof(selEnd)); + textControl->TextView()->Select(selStart, selEnd); + } + } + } +} + + +void +FindPanel::ResizeAttributeBox(const BNode *node) +{ + BBox *box = dynamic_cast(FindView("Box")); + BRect bounds(box->Bounds()); + int32 count = InitialAttrCount(node); + + bounds.bottom = count * 30 + 40; + box->ResizeTo(bounds.Width(), bounds.Height()); +} + + +void +FindPanel::AddByAttributeItems(const BNode *node) +{ + BBox *box = dynamic_cast(FindView("Box")); + ASSERT(box); + BRect bounds(box->Bounds()); + + int32 numAttributes = InitialAttrCount(node); + if (numAttributes < 1) + numAttributes = 1; + + BRect rect(bounds); + rect.InsetBy(5, 5); + rect.bottom = rect.top + 25; + + for (int32 index = 0; index < numAttributes; index ++) { + AddOneAttributeItem(box, rect); + rect.OffsetBy(0, 30); + } + SetUpAddRemoveButtons(box); +} + + +void +FindPanel::AddByNameOrFormulaItems() +{ + BBox *box = dynamic_cast(FindView("Box")); + + BRect bounds(box->Bounds()); + bounds.InsetBy(10, 10); + BTextControl *textControl = new BTextControl(bounds, "TextControl", "", "", NULL); + textControl->SetDivider(0.0f); + box->AddChild(textControl); + textControl->MakeFocus(); +} + + +void +FindPanel::RemoveAttrViewItems() +{ + for (;;) { + BView *view = FindView("AttrView"); + if (view == NULL) + break; + view->RemoveSelf(); + delete view; + } + + fAttrViewList.MakeEmpty(); +} + + +void +FindPanel::RemoveByAttributeItems() +{ + RemoveAttrViewItems(); + BView *view = FindView("add"); + if (view) { + view->RemoveSelf(); + delete view; + } + + view = FindView("remove"); + if (view) { + view->RemoveSelf(); + delete view; + } + + view = dynamic_cast(FindView("TextControl")); + if (view) { + view->RemoveSelf(); + delete view; + } +} + + +void +FindPanel::ShowOrHideMimeTypeMenu() +{ + BMenuField *menuField = dynamic_cast(FindView("MimeTypeMenu")); + if (Mode() == (int32)kByForumlaItem && !menuField->IsHidden()) + menuField->Hide(); + else if (menuField->IsHidden()) + menuField->Show(); +} + + +// #pragma mark - + + +TAttrView::TAttrView(BRect frame, int32 index) + : BView(frame, "AttrView", B_FOLLOW_NONE, B_WILL_DRAW) +{ + SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + + BPopUpMenu *menu = new BPopUpMenu("PopUp"); + + // add NAME attribute to popup + BMenu *submenu = new BMenu("Name"); + submenu->SetRadioMode(true); + submenu->SetFont(be_plain_font); + BMessage *message = new BMessage(kAttributeItemMain); + message->AddString("name", "name"); + message->AddInt32("type", B_STRING_TYPE); + BMenuItem *item = new BMenuItem(submenu, message); + menu->AddItem(item); + + const int32 operators[] = {B_CONTAINS, B_EQ, B_NE, B_BEGINS_WITH, B_ENDS_WITH}; + const char *operatorLabels[] = {"contains", "is", "is not", "starts with", "ends with"}; + + for (int32 i = 0;i < 5;i++) { + message = new BMessage(kAttributeItem); + message->AddInt32("operator", operators[i]); + submenu->AddItem(new BMenuItem(operatorLabels[i], message)); + } + + // mark first item + menu->ItemAt(0)->SetMarked(true); + submenu->ItemAt(0)->SetMarked(true); + + // add SIZE attribute + submenu = new BMenu("Size"); + submenu->SetRadioMode(true); + submenu->SetFont(be_plain_font); + message = new BMessage(kAttributeItemMain); + message->AddString("name", "size"); + message->AddInt32("type", B_OFF_T_TYPE); + item = new BMenuItem(submenu, message); + menu->AddItem(item); + + message = new BMessage(kAttributeItem); + message->AddInt32("operator", B_GE); + submenu->AddItem(new BMenuItem("greater than", message)); + + message = new BMessage(kAttributeItem); + message->AddInt32("operator", B_LE); + submenu->AddItem(new BMenuItem("less than", message)); + + message = new BMessage(kAttributeItem); + message->AddInt32("operator", B_EQ); + submenu->AddItem(new BMenuItem("is", message)); + + // add "modified" field + submenu = new BMenu("Modified"); + submenu->SetRadioMode(true); + submenu->SetFont(be_plain_font); + message = new BMessage(kAttributeItemMain); + message->AddString("name", "last_modified"); + message->AddInt32("type", B_TIME_TYPE); + item = new BMenuItem(submenu, message); + menu->AddItem(item); + + message = new BMessage(kAttributeItem); + message->AddInt32("operator", B_LE); + submenu->AddItem(new BMenuItem("before", message)); + + message = new BMessage(kAttributeItem); + message->AddInt32("operator", B_GE); + submenu->AddItem(new BMenuItem("after", message)); + + BRect bounds(Bounds()); + bounds.right = bounds.left + 100; + bounds.bottom = bounds.top + 15; + fMenuField = new BMenuField(bounds, "MenuField", "", menu); + fMenuField->SetDivider(0.0f); + + // add text entry box + bounds = Bounds(); + bounds.left += bounds.right - 180; + bounds.top += 2; + bounds.right -= 42; + BString title("TextEntry"); + title << index; + fTextControl = new BTextControl(bounds, title.String(), "", "", NULL); + fTextControl->SetDivider(0.0f); + AddChild(fTextControl); + + AddChild(fMenuField); + // add attributes from currently selected mimetype +} + + +TAttrView::~TAttrView() +{ +} + + +void +TAttrView::AttachedToWindow() +{ + BMenu *menu = fMenuField->Menu(); + // target everything + menu->SetTargetForItems(this); + + for (int32 index = menu->CountItems() - 1; index >= 0; index--) + menu->SubmenuAt(index)->SetTargetForItems(this); +} + + +void +TAttrView::MakeTextViewFocus() +{ + fTextControl->MakeFocus(); +} + + +void +TAttrView::RestoreState(const BMessage &message, int32 index) +{ + BMenu *menu = fMenuField->Menu(); + // decode menu selections + + AddMimeTypeAttrs(menu); + + const char *label; + if (message.FindString("menuSelection", index, &label) == B_OK) { + int32 itemIndex = SelectItemWithLabel(menu, label); + if (itemIndex >=0) { + menu = menu->SubmenuAt(itemIndex); + if (menu && message.FindString("subMenuSelection", index, &label) + == B_OK) + SelectItemWithLabel(menu, label); + } + } + + // decode attribute text + ASSERT(fTextControl); + const char *string; + if (message.FindString("attrViewText", index, &string) == B_OK) + fTextControl->TextView()->SetText(string); + + int32 logicMenuSelectedIndex; + BMenuField *field = dynamic_cast(FindView("Logic")); + if (message.FindInt32("logicalRelation", index, &logicMenuSelectedIndex) == B_OK) + if (field) + field->Menu()->ItemAt(logicMenuSelectedIndex)->SetMarked(true); + else + AddLogicMenu(logicMenuSelectedIndex == 0); +} + + +void +TAttrView::SaveState(BMessage *message, int32) +{ + BMenu *menu = fMenuField->Menu(); + + // encode main attribute menu selection + BMenuItem *item = menu->FindMarked(); + message->AddString("menuSelection", item ? item->Label() : ""); + + // encode submenu selection + const char *label = ""; + if (item) { + BMenu *submenu = menu->SubmenuAt(menu->IndexOf(item)); + if (submenu) { + item = submenu->FindMarked(); + if (item) + label = item->Label(); + } + } + message->AddString("subMenuSelection", label); + + // encode attribute text + ASSERT(fTextControl); + message->AddString("attrViewText", fTextControl->TextView()->Text()); + + BMenuField *field = dynamic_cast(FindView("Logic")); + if (field) { + BMenuItem *item = field->Menu()->FindMarked(); + ASSERT(item); + message->AddInt32("logicalRelation", item ? field->Menu()->IndexOf(item) : 0); + } +} + +void +TAttrView::AddLogicMenu(bool selectAnd) +{ + // add "AND/OR" menu + BPopUpMenu *menu = new BPopUpMenu(""); + BMessage *message = new BMessage(); + message->AddInt32("combine", B_AND); + BMenuItem *item = new BMenuItem("And", message); + menu->AddItem(item); + if (selectAnd) + item->SetMarked(true); + + message = new BMessage(); + message->AddInt32("combine", B_OR); + item = new BMenuItem("Or", message); + menu->AddItem(item); + if (!selectAnd) + item->SetMarked(true); + + menu->SetTargetForItems(this); + + BRect bounds(Bounds()); + bounds.left = bounds.right - 40; + bounds.bottom = bounds.top + 15; + BMenuField *menufield = new BMenuField(bounds, "Logic", "", menu); + menufield->SetDivider(0.0f); + menufield->HidePopUpMarker(); + AddChild(menufield); +} + + +void +TAttrView::RemoveLogicMenu() +{ + BMenuField *menufield = dynamic_cast(FindView("Logic")); + if (menufield) { + menufield->RemoveSelf(); + delete menufield; + } +} + + +void +TAttrView::Draw(BRect) +{ + BMenuItem *item = fMenuField->Menu()->FindMarked(); + if (!item) + return; + + if (item->Submenu()->FindMarked()) { + float width = StringWidth(item->Submenu()->FindMarked()->Label()); + BRect bounds(fTextControl->Frame()); + + // draws the is/contains, etc. string + bounds.left -= (width + 10); + bounds.bottom -= 6; + MovePenTo(bounds.LeftBottom()); + DrawString(item->Submenu()->FindMarked()->Label()); + } +} + + +void +TAttrView::MessageReceived(BMessage *message) +{ + BMenuItem *item; + + switch (message->what) { + case kAttributeItem: + if (message->FindPointer("source", (void **)&item) != B_OK) + return; + + item->Menu()->Superitem()->SetMarked(true); + Invalidate(); + break; + + case kAttributeItemMain: + // in case someone selected just and attribute without the + // comparator + if (message->FindPointer("source", (void **)&item) != B_OK) + return; + + if (item->Submenu()->ItemAt(0)) + item->Submenu()->ItemAt(0)->SetMarked(true); + Invalidate(); + break; + + default: + _inherited::MessageReceived(message); + break; + } +} + + +void +TAttrView::AddMimeTypeAttrs() +{ + BMenu *menu = fMenuField->Menu(); + AddMimeTypeAttrs(menu); +} + + +void +TAttrView::AddMimeTypeAttrs(BMenu *menu) +{ + FindPanel *mainView = dynamic_cast(Parent()-> + Parent()->FindView("MainView")); + if (!mainView) + return; + + const char *typeName; + if (mainView->CurrentMimeType(&typeName) == NULL) + return; + + BMimeType mimeType(typeName); + if (!mimeType.IsInstalled()) + return; + + // only add things to menu which have "user-visible" data + BMessage attributeMessage; + if (mimeType.GetAttrInfo(&attributeMessage) != B_OK) + return; + + char desc[B_MIME_TYPE_LENGTH]; + mimeType.GetShortDescription(desc); + + // go through each field in meta mime and add it to a menu + for (int32 index = 0; ; index++) { + const char *publicName; + if (attributeMessage.FindString("attr:public_name", index, &publicName) != B_OK) + break; + + if (!attributeMessage.FindBool("attr:viewable")) + continue; + + const char *attributeName; + if (attributeMessage.FindString("attr:name", index, &attributeName) != B_OK) + continue; + + int32 type; + if (attributeMessage.FindInt32("attr:type", index, &type) != B_OK) + continue; + + BMenu *submenu = new BMenu(publicName); + submenu->SetRadioMode(true); + submenu->SetFont(be_plain_font); + BMessage *message = new BMessage(kAttributeItemMain); + message->AddString("name", attributeName); + message->AddInt32("type", type); + BMenuItem *item = new BMenuItem(submenu, message); + menu->AddItem(item); + menu->SetTargetForItems(this); + + switch (type) { + case B_STRING_TYPE: + message = new BMessage(kAttributeItem); + message->AddInt32("operator", B_CONTAINS); + submenu->AddItem(new BMenuItem("contains", message)); + + message = new BMessage(kAttributeItem); + message->AddInt32("operator", B_EQ); + submenu->AddItem(new BMenuItem("is", message)); + + message = new BMessage(kAttributeItem); + message->AddInt32("operator", B_NE); + submenu->AddItem(new BMenuItem("is not", message)); + submenu->SetTargetForItems(this); + + message = new BMessage(kAttributeItem); + message->AddInt32("operator", B_BEGINS_WITH); + submenu->AddItem(new BMenuItem("starts with", message)); + submenu->SetTargetForItems(this); + + message = new BMessage(kAttributeItem); + message->AddInt32("operator", B_ENDS_WITH); + submenu->AddItem(new BMenuItem("ends with", message)); + break; + + case B_BOOL_TYPE: + case B_INT16_TYPE: + case B_UINT8_TYPE: + case B_INT8_TYPE: + case B_UINT16_TYPE: + case B_INT32_TYPE: + case B_UINT32_TYPE: + case B_INT64_TYPE: + case B_UINT64_TYPE: + case B_OFF_T_TYPE: + case B_FLOAT_TYPE: + case B_DOUBLE_TYPE: + message = new BMessage(kAttributeItem); + message->AddInt32("operator", B_EQ); + submenu->AddItem(new BMenuItem("is", message)); + + message = new BMessage(kAttributeItem); + message->AddInt32("operator", B_GE); + submenu->AddItem(new BMenuItem("greater than", message)); + + message = new BMessage(kAttributeItem); + message->AddInt32("operator", B_LE); + submenu->AddItem(new BMenuItem("less than", message)); + break; + + case B_TIME_TYPE: + message = new BMessage(kAttributeItem); + message->AddInt32("operator", B_LE); + submenu->AddItem(new BMenuItem("before", message)); + + message = new BMessage(kAttributeItem); + message->AddInt32("operator", B_GE); + submenu->AddItem(new BMenuItem("after", message)); + break; + } + submenu->SetTargetForItems(this); + } +} + + +void +TAttrView::GetDefaultName(BString &result) const +{ + BMenuItem *item = NULL; + if (fMenuField->Menu() != NULL) + item = fMenuField->Menu()->FindMarked(); + if (item != NULL) + result << item->Label(); + else + result << "Name"; + + if (item->Submenu() != NULL) + item = item->Submenu()->FindMarked(); + else + item = NULL; + + if (item != NULL) + result << " " << item->Label() << " "; + else + result << " = "; + + result << fTextControl->Text(); +} + + +// #pragma mark - + + +DeleteTransientQueriesTask::DeleteTransientQueriesTask() + : state(kInitial), + fWalker(NULL) +{ +} + + +DeleteTransientQueriesTask::~DeleteTransientQueriesTask() +{ + delete fWalker; +} + + +bool +DeleteTransientQueriesTask::DoSomeWork() +{ + switch (state) { + case kInitial: + Initialize(); + break; + + case kAllocatedWalker: + case kTraversing: + if (GetSome()) { + PRINT(("transient query killer done\n")); + return true; + } + break; + + case kError: + return true; + + } + return false; +} + + +void +DeleteTransientQueriesTask::Initialize() +{ + PRINT(("starting up transient query killer\n")); + BPath path; + status_t result = find_directory(B_USER_DIRECTORY, &path, false); + if (result != B_OK) { + state = kError; + return; + } + fWalker = new WALKER_NS::TNodeWalker(path.Path()); + state = kAllocatedWalker; +} + + +const int32 kBatchCount = 100; + +bool +DeleteTransientQueriesTask::GetSome() +{ + state = kTraversing; + for (int32 count = kBatchCount; count > 0; count--) { + entry_ref ref; + if (fWalker->GetNextRef(&ref) != B_OK) { + state = kError; + return true; + } + Model model(&ref); + if (model.IsQuery()) + ProcessOneRef(&model); +#if xDEBUG + else + PRINT(("transient query killer: %s not a query\n", model.Name())); +#endif + } + return false; +} + + +const int32 kDaysToExpire = 7; + +static bool +QueryOldEnough(Model *model) +{ + // check if it is old and ready to be deleted + time_t now = time(0); + + tm nowTimeData; + tm fileModData; + + localtime_r(&now, &nowTimeData); + localtime_r(&model->StatBuf()->st_ctime, &fileModData); + + if ((nowTimeData.tm_mday - fileModData.tm_mday) < kDaysToExpire + && (nowTimeData.tm_mday - fileModData.tm_mday) > -kDaysToExpire) { + PRINT(("query %s, not old enough\n", model->Name())); + return false; + } + return true; +} + + +bool +DeleteTransientQueriesTask::ProcessOneRef(Model *model) +{ + BModelOpener opener(model); + + // is this a temporary query + if (!MoreOptionsStruct::QueryTemporary(model->Node())) { + PRINT(("query %s, not temporary\n", model->Name())); + return false; + } + + if (!QueryOldEnough(model)) + return false; + + ASSERT(dynamic_cast(be_app)); + + // check that it is not showing + if (dynamic_cast(be_app)->EntryHasWindowOpen(model->EntryRef())) { + PRINT(("query %s, showing, can't delete\n", model->Name())); + return false; + } + + PRINT(("query %s, old, temporary, not shownig - deleting\n", model->Name())); + BEntry entry(model->EntryRef()); + entry.Remove(); + + return true; +} + + +class DeleteTransientQueriesFunctor : public FunctionObjectWithResult { +public: + DeleteTransientQueriesFunctor(DeleteTransientQueriesTask *task) + : task(task) + {} + + virtual ~DeleteTransientQueriesFunctor() + { + delete task; + } + + virtual void operator()() + { result = task->DoSomeWork(); } + +private: + DeleteTransientQueriesTask *task; +}; + + +void +DeleteTransientQueriesTask::StartUpTransientQueryCleaner() +{ + // set up a task that wakes up when the machine is idle and starts + // killing off old transient queries + DeleteTransientQueriesFunctor *worker + = new DeleteTransientQueriesFunctor(new DeleteTransientQueriesTask()); + TTracker *tracker = dynamic_cast(be_app); + ASSERT(tracker); + tracker->MainTaskLoop()->RunWhenIdle(worker, + 30 * 60 * 1000000, // half an hour initial delay + 5 * 60 * 1000000, // idle for five minutes + 10 * 1000000); +} + + +// #pragma mark - + + +RecentFindItemsMenu::RecentFindItemsMenu(const char *title, const BMessenger *target, + uint32 what) + : BMenu(title, B_ITEMS_IN_COLUMN), + fTarget(*target), + fWhat(what) +{ +} + + +void +RecentFindItemsMenu::AttachedToWindow() +{ + // re-populate the menu with fresh items + for (int32 index = CountItems() - 1; index >= 0; index--) + delete RemoveItem(index); + + FindPanel::AddRecentQueries(this, false, &fTarget, fWhat); + BMenu::AttachedToWindow(); +} + + +#if !B_BEOS_VERSION_DANO +_IMPEXP_TRACKER +#endif +BMenu * +TrackerBuildRecentFindItemsMenu(const char *title) +{ + BMessenger tracker(kTrackerSignature); + return new RecentFindItemsMenu(title, &tracker, B_REFS_RECEIVED); +} + + +// #pragma mark - + + +DraggableQueryIcon::DraggableQueryIcon(BRect frame, const char *name, + const BMessage *message, BMessenger messenger, uint32 resizeFlags, uint32 flags) + : DraggableIcon(frame, name, B_QUERY_MIMETYPE, B_LARGE_ICON, + message, messenger, resizeFlags, flags) +{ +} + + +bool +DraggableQueryIcon::DragStarted(BMessage *dragMessage) +{ + // override to substitute the user-specified query name + dragMessage->RemoveData("be:clip_name"); + + FindWindow *window = dynamic_cast(Window()); + ASSERT(window); + dragMessage->AddString("be:clip_name", + window->BackgroundView()->UserSpecifiedName() ? + window->BackgroundView()->UserSpecifiedName() : "New Query"); + + return true; +} + + +// #pragma mark - + + +MostUsedNames::MostUsedNames(const char *fileName, const char *directory, int32 maxCount) + : + fFileName(fileName), + fDirectory(directory), + fLoaded(false), + fCount(maxCount) +{ +} + + +MostUsedNames::~MostUsedNames() +{ + // only write back settings when we've been used + if (!fLoaded) + return; + + // write most used list to file + + BPath path; + if (find_directory(B_USER_SETTINGS_DIRECTORY, &path, true) != B_OK) + return; + + path.Append(fDirectory); + path.Append(fFileName); + BFile file(path.Path(), B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE); + if (file.InitCheck() == B_OK) { + for (int32 i = 0; i < fList.CountItems(); i++) { + list_entry *entry = static_cast(fList.ItemAt(i)); + + char line[B_FILE_NAME_LENGTH + 5]; + + // limit upper bound to react more dynamically to changes + if (--entry->count > 20) + entry->count = 20; + + // if the item hasn't been chosen in a while, remove it + // (but leave at least one item in the list) + if (entry->count < -10 && i > 0) + continue; + + sprintf(line, "%ld %s\n", entry->count, entry->name); + if (file.Write(line, strlen(line)) < B_OK) + break; + } + } + file.Unset(); + + // free data + + for (int32 i = fList.CountItems(); i-- > 0;) { + list_entry *entry = static_cast(fList.ItemAt(i)); + free(entry->name); + delete entry; + } +} + + +bool +MostUsedNames::ObtainList(BList *list) +{ + if (!list) + return false; + + if (!fLoaded) + UpdateList(); + + fLock.Lock(); + + list->MakeEmpty(); + for (int32 i = 0; i < fCount; i++) { + list_entry *entry = static_cast(fList.ItemAt(i)); + if (entry == NULL) + return true; + + list->AddItem(entry->name); + } + return true; +} + + +void +MostUsedNames::ReleaseList() +{ + fLock.Unlock(); +} + + +void +MostUsedNames::AddName(const char *name) +{ + fLock.Lock(); + + if (!fLoaded) + LoadList(); + + // remove last entry if there are more than + // 2*fCount entries in the list + + list_entry *entry = NULL; + + if (fList.CountItems() > fCount * 2) { + entry = static_cast(fList.RemoveItem(fList.CountItems() - 1)); + + // is this the name we want to add here? + if (strcmp(name, entry->name)) { + free(entry->name); + delete entry; + entry = NULL; + } else + fList.AddItem(entry); + } + + if (entry == NULL) { + for (int32 i = 0; (entry = static_cast(fList.ItemAt(i))) != NULL; i++) + if (!strcmp(entry->name, name)) + break; + } + + if (entry == NULL) { + entry = new list_entry; + entry->name = strdup(name); + entry->count = 1; + + fList.AddItem(entry); + } else if (entry->count < 0) + entry->count = 1; + else + entry->count++; + + fLock.Unlock(); + UpdateList(); +} + + +int +MostUsedNames::CompareNames(const void *a,const void *b) +{ + list_entry *entryA = *(list_entry **)a; + list_entry *entryB = *(list_entry **)b; + + if (entryA->count == entryB->count) + return strcasecmp(entryA->name,entryB->name); + + return entryB->count - entryA->count; +} + + +void +MostUsedNames::LoadList() +{ + if (fLoaded) + return; + fLoaded = true; + + // load the most used names list + + BPath path; + if (find_directory(B_USER_SETTINGS_DIRECTORY, &path, true) != B_OK) + return; + + path.Append(fDirectory); + path.Append(fFileName); + + FILE *file = fopen(path.Path(), "r"); + if (file == NULL) + return; + + char line[B_FILE_NAME_LENGTH + 5]; + while (fgets(line, sizeof(line), file) != NULL) { + int32 length = (int32)strlen(line) - 1; + if (length >= 0 && line[length] == '\n') + line[length] = '\0'; + + int32 count = atoi(line); + + char *name = strchr(line, ' '); + if (name == NULL || *(++name) == '\0') + continue; + + list_entry *entry = new list_entry; + entry->name = strdup(name); + entry->count = count; + + fList.AddItem(entry); + } + fclose(file); +} + + +void +MostUsedNames::UpdateList() +{ + AutoLock locker(fLock); + + if (!fLoaded) + LoadList(); + + // sort list items + + fList.SortItems(MostUsedNames::CompareNames); +} + +} // namespace BPrivate + diff --git a/src/kits/tracker/FindPanel.h b/src/kits/tracker/FindPanel.h new file mode 100644 index 0000000000..97c487784b --- /dev/null +++ b/src/kits/tracker/FindPanel.h @@ -0,0 +1,375 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef _FIND_PANEL_H +#define _FIND_PANEL_H + + +#include +#include +#include + + +#include "DialogPane.h" +#include "ObjectList.h" +#include "MimeTypeList.h" +#include "Utilities.h" +#include "NodeWalker.h" + +class BFilePanel; +class BQuery; +class BBox; +class BTextControl; +class BCheckBox; +class BMenuField; +class BFile; + +namespace BPrivate { + +class FindPanel; +class Model; +class DraggableIcon; +class TAttrView; + +const uint32 kVolumeItem = 'Fvol'; +const uint32 kAttributeItemMain = 'Fatr'; +const uint32 kByNameItem = 'Fbyn'; +const uint32 kByAttributeItem = 'Fbya'; +const uint32 kByForumlaItem = 'Fbyq'; +const uint32 kAddItem = 'Fadd'; +const uint32 kRemoveItem = 'Frem'; + +#if !B_BEOS_VERSION_DANO +_IMPEXP_TRACKER +#endif +BMenu *TrackerBuildRecentFindItemsMenu(const char *); + +struct MoreOptionsStruct { + bool showMoreOptions; + bool searchTrash; + int32 reserved1; + bool temporary; + bool reserved9; + bool reserved10; + bool reserved11; + int32 reserved3; + int32 reserved4; + int32 reserved5; + int32 reserved6; + int32 reserved7; + int32 reserved8; + // reserve a bunch of fields so that we can add stuff later but not + // make old queries incompatible. Reserved fields are set to 0 when + // saved + + MoreOptionsStruct() + : showMoreOptions(false), + searchTrash(false), + reserved1(0), + temporary(true), + reserved9(false), + reserved10(false), + reserved11(false), + reserved3(0), + reserved4(0), + reserved5(0), + reserved6(0), + reserved7(0), + reserved8(0) + {} + + static void EndianSwap(void *castToThis); + + static void SetQueryTemporary(BNode *, bool on); + static bool QueryTemporary(const BNode *); +}; + + +class FindWindow : public BWindow { + public: + FindWindow(const entry_ref * = NULL, bool editIfTemplateOnly = false); + virtual ~FindWindow(); + + FindPanel *BackgroundView() const + { return fBackground; } + + BNode *QueryNode() const + { return fFile; } + + const char *QueryName() const; + // reads in the query name from either a saved name in a template or + // form a saved query name + + static bool IsQueryTemplate(BNode *); + + protected: + virtual void MessageReceived(BMessage *); + + private: + static BFile *TryOpening(const entry_ref *); + static void GetDefaultQuery(BEntry &entry); + // when opening an empty panel, use the default query to set the panel up + void SaveQueryAttributes(BNode *, bool templateQuery); + + void Find(); + // retrieve the results + void Save(); + // save the contents of the find window into the query file + + void SwitchToTemplate(const entry_ref *); + bool FindSaveCommon(bool find); + + status_t SaveQueryAsAttributes(BNode *, BEntry *, bool queryTemplate, + const BMessage *oldAttributes = 0, const BPoint *oldLocation = 0); + + void GetDefaultName(BString &); + void GetPredicateString(BString &, bool &dynamicDate); + // dynamic date is a date such as 'today' + + BFile *fFile; + entry_ref fRef; + bool fFromTemplate; + bool fEditTemplateOnly; + FindPanel *fBackground; + mutable BString fQueryNameFromTemplate; + BFilePanel *fSaveAsTemplatePanel; + + typedef BWindow _inherited; +}; + + +class FindPanel : public BView { + public: + FindPanel(BRect, BFile *, FindWindow *parent, bool fromTemplate, + bool editTemplateOnly); + virtual ~FindPanel(); + + virtual void AttachedToWindow(); + virtual void MessageReceived(BMessage*); + + void BuildAttrQuery(BQuery *, bool &dynamicDate) const; + BPopUpMenu *MimeTypeMenu() const + { return fMimeTypeMenu; } + BMenuItem *CurrentMimeType(const char **type = NULL) const; + status_t SetCurrentMimeType(BMenuItem *item); + status_t SetCurrentMimeType(const char *label); + + BPopUpMenu *VolMenu() const + { return fVolMenu; } + uint32 Mode() const + { return fMode; } + + static BRect InitialViewSize(const BNode *); + // used when showing window, does not account for more options, + // those if used will force a resize later + + static uint32 InitialMode(const BNode *entry); + void SaveWindowState(BNode *, bool editTemplate); + + void SwitchToTemplate(const BNode *); + + void GetByAttrPredicate(BQuery *, bool &dynamicDate) const; + // build up a query from by-attribute items + void GetByNamePredicate(BQuery *) const; + // build up a simple query from the name we are searching for + + void GetDefaultName(BString &) const; + const char *UserSpecifiedName() const; + // name filled out in the query name text field + + static void AddRecentQueries(BMenu *, bool addSaveAsItem, + const BMessenger *target, uint32 what); + // populate the recent query menu with query templates and recent + // queries + + private: + static float ViewHeightForMode(uint32 mode, bool moreOptions); + // accouts for moreOptions + // if in attributeView, only returns valid result if one attr only + static float BoxHeightForMode(uint32 mode, bool moreOptions); + + void AddMimeTypesToMenu(); + // populates the type menu + static bool AddOneMimeTypeToMenu(const ShortMimeInfo *, void *); + + void AddVolumes(BMenu *); + // populates the volume menu + void ShowVolumeMenuLabel(); + + void AddAttrView(); + // add one more attribute item to the attr view + void RemoveAttrView(); + // remove the last attribute item + void AddFirstAttr(); + + // panel building/restoring calls + void RestoreWindowState(const BNode *); + void RestoreMimeTypeMenuSelection(const BNode *); + void AddByAttributeItems(const BNode *); + void ResizeAttributeBox(const BNode *); + void RemoveByAttributeItems(); + void RemoveAttrViewItems(); + void ShowOrHideMimeTypeMenu(); + // MimeTypeWindow is only shown in kByNameItem and kByAttributeItem modes + + void ShowOrHideMoreOptions(bool show); + // fMode gets set by this and the call relies on it being up-to-date + static int32 InitialAttrCount(const BNode *); + void FillCurrentQueryName(BTextControl *, FindWindow *); + void AddByNameOrFormulaItems(); + void AddOneAttributeItem(BBox *box, BRect); + void SetUpAddRemoveButtons(BBox *box); + + void SwitchMode(uint32); + // go from search by name to search by attribute, etc. + + void PushMimeType(BQuery *query) const; + + void SaveAsQueryOrTemplate(const entry_ref *, const char *, bool queryTemplate); + + uint32 fMode; + BObjectList fAttrViewList; + BPopUpMenu *fMimeTypeMenu; + BMenuField *fMimeTypeField; + BPopUpMenu *fVolMenu; + BPopUpMenu *fSearchModeMenu; + BPopUpMenu *fRecentQueries; + DialogPane *fMoreOptionsPane; + BTextControl *fQueryName; + BString fInitialQueryName; + + BCheckBox *fTemporaryCheck; + BCheckBox *fSearchTrashCheck; + + PaneSwitch *fLatch; + DraggableIcon *fDraggableIcon; + + typedef BView _inherited; + + friend class RecentQueriesPopUp; +}; + +class TAttrView : public BView { + // a single attribute item - the search by attribute view + // can add several of these + public: + TAttrView(BRect, int32 index); + ~TAttrView(); + + virtual void AttachedToWindow(); + + void RestoreState(const BMessage &, int32); + void SaveState(BMessage *, int32); + + virtual void Draw(BRect); + virtual void MessageReceived(BMessage *); + + void AddLogicMenu(bool selectAnd = true); + void RemoveLogicMenu(); + void AddMimeTypeAttrs(); + void MakeTextViewFocus(); + + void GetDefaultName(BString &result) const; + + private: + void AddMimeTypeAttrs(BMenu *); + + BMenuField *fMenuField; + BTextControl *fTextControl; + + typedef BView _inherited; +}; + + +class DeleteTransientQueriesTask { + // transient queries get deleted if they didn't get used in a while; + // this is the task that takes care of it + public: + static void StartUpTransientQueryCleaner(); + + bool DoSomeWork(); + virtual ~DeleteTransientQueriesTask(); + + protected: + DeleteTransientQueriesTask(); + // returns true when done + + enum State { + kInitial, + kAllocatedWalker, + kTraversing, + kError + }; + + State state; + + void Initialize(); + bool GetSome(); + + bool ProcessOneRef(Model *); + + private: + WALKER_NS::TNodeWalker *fWalker; +}; + + +class RecentFindItemsMenu : public BMenu { + public: + RecentFindItemsMenu(const char *title, const BMessenger *target, uint32 what); + + protected: + virtual void AttachedToWindow(); + + private: + BMessenger fTarget; + uint32 fWhat; +}; + + +class DraggableQueryIcon : public DraggableIcon { + // query/query template drag&drop helper + public: + DraggableQueryIcon(BRect frame, const char *name, const BMessage *message, + BMessenger target, uint32 resizeFlags = B_FOLLOW_LEFT | B_FOLLOW_TOP, + uint32 flags = B_WILL_DRAW); + + protected: + virtual bool DragStarted(BMessage *); +}; + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/FunctionObject.h b/src/kits/tracker/FunctionObject.h new file mode 100644 index 0000000000..228cfd3ed2 --- /dev/null +++ b/src/kits/tracker/FunctionObject.h @@ -0,0 +1,536 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef __FUNCTION_OBJECT__ +#define __FUNCTION_OBJECT__ + +#include +#include + +#include +#include + +// parameter binders serve to store a copy of a struct and +// pass it in and out by pointers, allowing struct parameters to share +// the same syntax as scalar ones + +// You will mostly want to use the NewFunctionObject... convenience +// factories +// + +// add more function objects and specialized binders as needed + +namespace BPrivate { + +template +class ParameterBinder { +// primitive default binder for scalars +public: + ParameterBinder() {} + ParameterBinder(P p) + : p(p) + {} + + P Pass() const + { return p; } +private: + P p; +}; + +template<> +class ParameterBinder { +public: + ParameterBinder() {} + ParameterBinder(const BEntry *p) + : p(*p) + {} + + ParameterBinder &operator=(const BEntry *newp) + { p = *newp; return *this; } + + const BEntry *Pass() const + { return &p; } +private: + BEntry p; +}; + +template<> +class ParameterBinder { +public: + ParameterBinder() {} + ParameterBinder(const entry_ref *p) + { + if (p) + this->p = *p; + } + + ParameterBinder &operator=(const entry_ref *newp) + { p = *newp; return *this; } + + const entry_ref *Pass() const + { return &p; } +private: + entry_ref p; +}; + +template<> +class ParameterBinder { +public: + ParameterBinder() {} + ParameterBinder(const node_ref * p) + : p(*p) + {} + + ParameterBinder &operator=(const node_ref *newp) + { p = *newp; return *this; } + + const node_ref *Pass() const + { return &p; } +private: + node_ref p; +}; + +template<> +class ParameterBinder { +public: + ParameterBinder() {} + ParameterBinder(const BMessage *p) + : p(p ? new BMessage(*p) : NULL) + {} + + ~ParameterBinder() + { + delete p; + } + + ParameterBinder &operator=(const BMessage *newp) + { + delete p; + p = (newp ? new BMessage(*newp) : NULL); + return *this; + } + + const BMessage *Pass() const + { return p; } + +private: + BMessage *p; +}; + +class FunctionObject { +public: + virtual void operator()() = 0; + virtual ~FunctionObject() {} +}; + +template +class FunctionObjectWithResult : public FunctionObject { +public: + const R &Result() const + { return result; } + +protected: + R result; +}; + +template +class SingleParamFunctionObject : public FunctionObject { +public: + SingleParamFunctionObject(void (*callThis)(Param1), + Param1 p1) + : function(callThis), + p1(p1) + { + } + + + virtual void operator()() + { (function)(p1.Pass()); } + +private: + void (*function)(Param1); + ParameterBinder p1; +}; + +template +class SingleParamFunctionObjectWithResult : public FunctionObjectWithResult { +public: + SingleParamFunctionObjectWithResult(Result (*function)(Param1), Param1 p1) + : function(function), + p1(p1) + { + } + + + virtual void operator()() + { result = (function)(p1.Pass()); } + +private: + Result (*function)(Param1); + ParameterBinder p1; +}; + +template +class TwoParamFunctionObject : public FunctionObject { +public: + TwoParamFunctionObject(void (*callThis)(Param1, Param2), + Param1 p1, Param2 p2) + : function(callThis), + p1(p1), + p2(p2) + { + } + + virtual void operator()() + { (function)(p1.Pass(), p2.Pass()); } + +private: + void (*function)(Param1, Param2); + ParameterBinder p1; + ParameterBinder p2; +}; + + +template +class ThreeParamFunctionObject : public FunctionObject { +public: + ThreeParamFunctionObject(void (*callThis)(Param1, Param2, Param3), + Param1 p1, Param2 p2, Param3 p3) + : function(callThis), + p1(p1), + p2(p2), + p3(p3) + { + } + + + virtual void operator()() + { (function)(p1.Pass(), p2.Pass(), p3.Pass()); } + +private: + void (*function)(Param1, Param2, Param3); + ParameterBinder p1; + ParameterBinder p2; + ParameterBinder p3; +}; + +template +class ThreeParamFunctionObjectWithResult : public FunctionObjectWithResult { +public: + ThreeParamFunctionObjectWithResult(Result (*callThis)(Param1, Param2, Param3), + Param1 p1, Param2 p2, Param3 p3) + : function(callThis), + p1(p1), + p2(p2), + p3(p3) + { + } + + virtual void operator()() + { result = (function)(p1.Pass(), p2.Pass(), p3.Pass()); } + +private: + Result (*function)(Param1, Param2, Param3); + ParameterBinder p1; + ParameterBinder p2; + ParameterBinder p3; +}; + +template +class FourParamFunctionObject : public FunctionObject { +public: + FourParamFunctionObject(void (*callThis)(Param1, Param2, Param3, Param4), + Param1 p1, Param2 p2, Param3 p3, Param4 p4) + : function(callThis), + p1(p1), + p2(p2), + p3(p3), + p4(p4) + { + } + + virtual void operator()() + { (function)(p1.Pass(), p2.Pass(), p3.Pass(), p4.Pass()); } + +private: + void (*function)(Param1, Param2, Param3, Param4); + ParameterBinder p1; + ParameterBinder p2; + ParameterBinder p3; + ParameterBinder p4; +}; + +template +class FourParamFunctionObjectWithResult : public FunctionObjectWithResult { +public: + FourParamFunctionObjectWithResult(Result (*callThis)(Param1, Param2, Param3, Param4), + Param1 p1, Param2 p2, Param3 p3, Param4 p4) + : function(callThis), + p1(p1), + p2(p2), + p3(p3), + p4(p4) + { + } + + virtual void operator()() + { result = (function)(p1.Pass(), p2.Pass(), p3.Pass(), p4.Pass()); } + +private: + Result (*function)(Param1, Param2, Param3, Param4); + ParameterBinder p1; + ParameterBinder p2; + ParameterBinder p3; + ParameterBinder p4; +}; + +template +class PlainMemberFunctionObject : public FunctionObject { +public: + PlainMemberFunctionObject(void (T::*function)(), T *onThis) + : function(function), + target(onThis) + { + } + + virtual void operator()() + { (target->*function)(); } + +private: + void (T::*function)(); + T *target; +}; + +template +class PlainLockingMemberFunctionObject : public FunctionObject { +public: + PlainLockingMemberFunctionObject(void (T::*function)(), T *target) + : function(function), + messenger(target) + { + } + + virtual void operator()() + { + T *target = dynamic_cast(messenger.Target(NULL)); + if (!target || !messenger.LockTarget()) + return; + (target->*function)(); + target->Looper()->Unlock(); + } + +private: + void (T::*function)(); + BMessenger messenger; +}; + +template +class PlainMemberFunctionObjectWitResult : public FunctionObjectWithResult { +public: + PlainMemberFunctionObjectWitResult(R (T::*function)(), T *onThis) + : function(function), + target(onThis) + { + } + + virtual void operator()() + { result = (target->*function)(); } + + +private: + R (T::*function)(); + T *target; +}; + +template +class SingleParamMemberFunctionObject : public FunctionObject { +public: + SingleParamMemberFunctionObject(void (T::*function)(Param1), T *onThis, Param1 p1) + : function(function), + target(onThis), + p1(p1) + { + } + + virtual void operator()() + { (target->*function)(p1.Pass()); } + +private: + void (T::*function)(Param1); + T *target; + ParameterBinder p1; +}; + +template +class TwoParamMemberFunctionObject : public FunctionObject { +public: + TwoParamMemberFunctionObject(void (T::*function)(Param1, Param2), T *onThis, + Param1 p1, Param2 p2) + : function(function), + target(onThis), + p1(p1), + p2(p2) + { + } + + virtual void operator()() + { (target->*function)(p1.Pass(), p2.Pass()); } + + +protected: + void (T::*function)(Param1, Param2); + T *target; + ParameterBinder p1; + ParameterBinder p2; +}; + + +template +class SingleParamMemberFunctionObjectWitResult : public FunctionObjectWithResult { +public: + SingleParamMemberFunctionObjectWitResult(R (T::*function)(Param1), T *onThis, + Param1 p1) + : function(function), + target(onThis), + p1(p1) + { + } + + virtual void operator()() + { result = (target->*function)(p1.Pass()); } + +protected: + R (T::*function)(Param1); + T *target; + ParameterBinder p1; +}; + +template +class TwoParamMemberFunctionObjectWithResult : public FunctionObjectWithResult { +public: + TwoParamMemberFunctionObjectWithResult(R (T::*function)(Param1, Param2), T *onThis, + Param1 p1, Param2 p2) + : function(function), + target(onThis), + p1(p1), + p2(p2) + { + } + + virtual void operator()() + { result = (target->*function)(p1.Pass(), p2.Pass()); } + +protected: + R (T::*function)(Param1, Param2); + T *target; + ParameterBinder p1; + ParameterBinder p2; +}; + +// convenience factory functions +// NewFunctionObject +// NewMemberFunctionObject +// NewMemberFunctionObjectWithResult +// NewLockingMemberFunctionObject +// +// ... add the missing ones as needed + +template +SingleParamFunctionObject * +NewFunctionObject(void (*function)(Param1), Param1 p1) +{ + return new SingleParamFunctionObject(function, p1); +} + +template +TwoParamFunctionObject * +NewFunctionObject(void (*function)(Param1, Param2), Param1 p1, Param2 p2) +{ + return new TwoParamFunctionObject(function, p1, p2); +} + +template +ThreeParamFunctionObject * +NewFunctionObject(void (*function)(Param1, Param2, Param3), + Param1 p1, Param2 p2, Param3 p3) +{ + return new ThreeParamFunctionObject(function, p1, p2, p3); +} + +template +PlainMemberFunctionObject * +NewMemberFunctionObject(void (T::*function)(), T *onThis) +{ + return new PlainMemberFunctionObject(function, onThis); +} + +template +SingleParamMemberFunctionObject * +NewMemberFunctionObject(void (T::*function)(Param1), T *onThis, Param1 p1) +{ + return new SingleParamMemberFunctionObject(function, onThis, p1); +} + +template +TwoParamMemberFunctionObject * +NewMemberFunctionObject(void (T::*function)(Param1, Param2), T *onThis, + Param1 p1, Param2 p2) +{ + return new TwoParamMemberFunctionObject(function, onThis, + p1, p2); +} + +template +TwoParamMemberFunctionObjectWithResult * +NewMemberFunctionObjectWithResult(R (T::*function)(Param1, Param2), + T *onThis, Param1 p1, Param2 p2) +{ + return new TwoParamMemberFunctionObjectWithResult + (function, onThis, p1, p2); +} + +template +PlainLockingMemberFunctionObject * +NewLockingMemberFunctionObject(void (HandlerOrSubclass::*function)(), + HandlerOrSubclass *onThis) +{ + return new PlainLockingMemberFunctionObject(function, onThis); +} + +} // namespace BPrivate + +using namespace BPrivate; + +#endif + diff --git a/src/kits/tracker/GroupedMenu.cpp b/src/kits/tracker/GroupedMenu.cpp new file mode 100644 index 0000000000..522d04f0ed --- /dev/null +++ b/src/kits/tracker/GroupedMenu.cpp @@ -0,0 +1,323 @@ +#include "GroupedMenu.h" + +#include +#include + + +using namespace BPrivate; + + +TMenuItemGroup::TMenuItemGroup(const char *name) + : + fMenu(NULL), + fFirstItemIndex(-1), + fItemsTotal(0), + fHasSeparator(false) +{ + if (name != NULL && name[0] != '\0') + fName = strdup(name); + else + fName = NULL; +} + + +TMenuItemGroup::~TMenuItemGroup() +{ + free((char *)fName); + + if (fMenu == NULL) { + BMenuItem *item; + while ((item = RemoveItem(0L)) != NULL) + delete item; + } +} + + +bool +TMenuItemGroup::AddItem(BMenuItem *item) +{ + if (!fList.AddItem(item)) + return false; + + if (fMenu) + fMenu->AddGroupItem(this, item, fList.IndexOf(item)); + + fItemsTotal++; + return true; +} + + +bool +TMenuItemGroup::AddItem(BMenuItem *item, int32 atIndex) +{ + if (!fList.AddItem(item, atIndex)) + return false; + + if (fMenu) + fMenu->AddGroupItem(this, item, atIndex); + + fItemsTotal++; + return true; +} + + +bool +TMenuItemGroup::AddItem(BMenu *menu) +{ + BMenuItem *item = new BMenuItem(menu); + if (item == NULL) + return false; + + if (!AddItem(item)) { + delete item; + return false; + } + + return true; +} + + +bool +TMenuItemGroup::AddItem(BMenu *menu, int32 atIndex) +{ + BMenuItem *item = new BMenuItem(menu); + if (item == NULL) + return false; + + if (!AddItem(item, atIndex)) { + delete item; + return false; + } + + return true; +} + + +bool +TMenuItemGroup::RemoveItem(BMenuItem *item) +{ + if (fMenu) + fMenu->RemoveGroupItem(this, item); + + return fList.RemoveItem(item); +} + + +bool +TMenuItemGroup::RemoveItem(BMenu *menu) +{ + BMenuItem *item = menu->Superitem(); + if (item == NULL) + return false; + + return RemoveItem(item); +} + + +BMenuItem * +TMenuItemGroup::RemoveItem(int32 index) +{ + BMenuItem *item = ItemAt(index); + if (item == NULL) + return false; + + if (RemoveItem(item)) + return item; + + return NULL; +} + + +BMenuItem * +TMenuItemGroup::ItemAt(int32 index) +{ + return static_cast(fList.ItemAt(index)); +} + + +int32 +TMenuItemGroup::CountItems() +{ + return fList.CountItems(); +} + + +void +TMenuItemGroup::Separated(bool separated) +{ + if (separated == fHasSeparator) + return; + + fHasSeparator = separated; + + if (separated) + fItemsTotal++; + else + fItemsTotal--; +} + + +bool +TMenuItemGroup::HasSeparator() +{ + return fHasSeparator; +} + + +// #pragma mark - + + +TGroupedMenu::TGroupedMenu(const char *name) + : BMenu(name) +{ +} + + +TGroupedMenu::~TGroupedMenu() +{ + TMenuItemGroup *group; + while ((group = static_cast(fGroups.RemoveItem(0L))) != NULL) + delete group; +} + + +bool +TGroupedMenu::AddGroup(TMenuItemGroup *group) +{ + if (!fGroups.AddItem(group)) + return false; + + group->fMenu = this; + + for (int32 i = 0; i < group->CountItems(); i++) { + AddGroupItem(group, group->ItemAt(i), i); + } + + return true; +} + + +bool +TGroupedMenu::AddGroup(TMenuItemGroup *group, int32 atIndex) +{ + if (!fGroups.AddItem(group, atIndex)) + return false; + + group->fMenu = this; + + for (int32 i = 0; i < group->CountItems(); i++) { + AddGroupItem(group, group->ItemAt(i), i); + } + + return true; +} + + +bool +TGroupedMenu::RemoveGroup(TMenuItemGroup *group) +{ + if (group->HasSeparator()) { + delete RemoveItem(group->fFirstItemIndex); + group->Separated(false); + } + + group->fMenu = NULL; + group->fFirstItemIndex = -1; + + for (int32 i = 0; i < group->CountItems(); i++) { + RemoveItem(group->ItemAt(i)); + } + + return fGroups.RemoveItem(group); +} + + +TMenuItemGroup * +TGroupedMenu::GroupAt(int32 index) +{ + return static_cast(fGroups.ItemAt(index)); +} + + +int32 +TGroupedMenu::CountGroups() +{ + return fGroups.CountItems(); +} + + +void +TGroupedMenu::AddGroupItem(TMenuItemGroup *group, BMenuItem *item, int32 atIndex) +{ + int32 groupIndex = fGroups.IndexOf(group); + bool addSeparator = false; + + if (group->fFirstItemIndex == -1) { + // find new home for this group + if (groupIndex > 0) { + // add this group after an existing one + TMenuItemGroup *previous = GroupAt(groupIndex - 1); + group->fFirstItemIndex = previous->fFirstItemIndex + previous->fItemsTotal; + addSeparator = true; + } else { + // this is the first group + TMenuItemGroup *successor = GroupAt(groupIndex + 1); + if (successor != NULL) { + group->fFirstItemIndex = successor->fFirstItemIndex; + if (successor->fHasSeparator) { + // we'll need one as well + addSeparator = true; + } + } else { + group->fFirstItemIndex = CountItems(); + if (group->fFirstItemIndex > 0) + addSeparator = true; + } + } + + if (addSeparator) { + AddItem(new BSeparatorItem(), group->fFirstItemIndex); + group->Separated(true); + } + } + + // insert item for real + + AddItem(item, atIndex + group->fFirstItemIndex + (group->HasSeparator() ? 1 : 0)); + + // move the groups after this one + + for (int32 i = groupIndex + 1; i < CountGroups(); i++) { + group = GroupAt(i); + group->fFirstItemIndex += addSeparator ? 2 : 1; + } +} + + +void +TGroupedMenu::RemoveGroupItem(TMenuItemGroup *group, BMenuItem *item) +{ + int32 groupIndex = fGroups.IndexOf(group); + bool removedSeparator = false; + + if (group->CountItems() == 1) { + // this is the last item + if (group->HasSeparator()) { + RemoveItem(group->fFirstItemIndex); + group->Separated(false); + removedSeparator = true; + } + } + + // insert item for real + + RemoveItem(item); + + // move the groups after this one + + for (int32 i = groupIndex + 1; i < CountGroups(); i++) { + group = GroupAt(i); + group->fFirstItemIndex -= removedSeparator ? 2 : 1; + } +} + diff --git a/src/kits/tracker/GroupedMenu.h b/src/kits/tracker/GroupedMenu.h new file mode 100644 index 0000000000..ab6e2e765a --- /dev/null +++ b/src/kits/tracker/GroupedMenu.h @@ -0,0 +1,70 @@ +#ifndef GROUPED_MENU_H +#define GROUPED_MENU_H + + +#include +#include +#include + + +namespace BPrivate { + +class TGroupedMenu; + +class TMenuItemGroup { + public: + TMenuItemGroup(const char *name); + ~TMenuItemGroup(); + + bool AddItem(BMenuItem *item); + bool AddItem(BMenuItem *item, int32 atIndex); + bool AddItem(BMenu *menu); + bool AddItem(BMenu *menu, int32 atIndex); + + bool RemoveItem(BMenuItem *item); + bool RemoveItem(BMenu *menu); + BMenuItem *RemoveItem(int32 index); + + BMenuItem *ItemAt(int32 index); + int32 CountItems(); + + private: + friend class TGroupedMenu; + void Separated(bool separated); + bool HasSeparator(); + + private: + const char *fName; + BList fList; + TGroupedMenu *fMenu; + int32 fFirstItemIndex; + int32 fItemsTotal; + bool fHasSeparator; +}; + + +class TGroupedMenu : public BMenu { + public: + TGroupedMenu(const char *name); + ~TGroupedMenu(); + + bool AddGroup(TMenuItemGroup *group); + bool AddGroup(TMenuItemGroup *group, int32 atIndex); + + bool RemoveGroup(TMenuItemGroup *group); + + TMenuItemGroup *GroupAt(int32 index); + int32 CountGroups(); + + private: + friend class TMenuItemGroup; + void AddGroupItem(TMenuItemGroup *group, BMenuItem *item, int32 atIndex); + void RemoveGroupItem(TMenuItemGroup *group, BMenuItem *item); + + private: + BList fGroups; +}; + +} // namespace BPrivate + +#endif /* GROUPED_MENU_H */ diff --git a/src/kits/tracker/IconCache.cpp b/src/kits/tracker/IconCache.cpp new file mode 100644 index 0000000000..c835c70b0b --- /dev/null +++ b/src/kits/tracker/IconCache.cpp @@ -0,0 +1,1850 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// Icon cache is used for drawing node icons; it caches icons +// and reuses them for successive draws + +// +// Possible performance improvements: +// - Mime API requires BBitmaps to retrieve bits and successive +// SetBits that cause app server contention +// Consider having special purpose "give me just the bits" calls +// to deal with that. +// - Related to this, node cache entries would only store the raw bits +// to cut down on number of BBitmaps and related overhead +// - Make the cache miss and fill case for the shared cache reuse the +// already calculated hash value +// +// Other ToDo items: +// Use lazily allocated bitmap arrays for every view for node icon cache +// drawing +// Have an overflow list for deleting shared icons, delete from the list +// every now and then + + +// Actual icon lookup sequence: +// icon from node +// preferred app for node -> icon for type +// preferred app for type -> icon for type +// metamime -> icon for type +// preferred app for supertype -> icon for type +// supertype metamime -> icon for type +// generic icon + + +#include +#include +#include +#include + +#include "Bitmaps.h" +#include "FSUtils.h" +#include "IconCache.h" +#include "MimeTypes.h" +#include "Model.h" + +#if DEBUG +// #define LOG_DISK_HITS +// the LOG_DISK_HITS define is used to check that the disk is not hit more +// than needed - enable it, open a window with a bunch of poses, force +// it to redraw, shouldn't recache +// #define LOG_ADD_ITEM +#endif + +// set up a few printing macros to get rid of a ton of debugging ifdefs in the code +#ifdef LOG_DISK_HITS + #define PRINT_DISK_HITS(ARGS) _debugPrintf ARGS +#else + #define PRINT_DISK_HITS(ARGS) (void)0 +#endif + +#ifdef LOG_ADD_ITEM + #define PRINT_ADD_ITEM(ARGS) _debugPrintf ARGS +#else + #define PRINT_ADD_ITEM(ARGS) (void)0 +#endif + +#undef NODE_CACHE_ASYNC_DRAWS + + +IconCacheEntry::IconCacheEntry() + : fLargeIcon(NULL), + fMiniIcon(NULL), + fHilitedLargeIcon(NULL), + fHilitedMiniIcon(NULL), + fAliasForIndex(-1) +{ +} + + +IconCacheEntry::~IconCacheEntry() +{ + if (fAliasForIndex < 0) { + delete fLargeIcon; + delete fMiniIcon; + delete fHilitedLargeIcon; + delete fHilitedMiniIcon; + + // clean up a bit to leave the hash table entry in an initialized state + fLargeIcon = NULL; + fMiniIcon = NULL; + fHilitedLargeIcon = NULL; + fHilitedMiniIcon = NULL; + + } + fAliasForIndex = -1; +} + + +void +IconCacheEntry::SetAliasFor(const SharedIconCache *sharedCache, + const SharedCacheEntry *entry) +{ + sharedCache->SetAliasFor(this, entry); + ASSERT(fAliasForIndex >= 0); +} + + +IconCacheEntry * +IconCacheEntry::ResolveIfAlias(const SharedIconCache *sharedCache) +{ + return sharedCache->ResolveIfAlias(this); +} + + +IconCacheEntry * +IconCacheEntry::ResolveIfAlias(const SharedIconCache *sharedCache, + IconCacheEntry *entry) +{ + if (!entry) + return NULL; + + return sharedCache->ResolveIfAlias(entry); +} + + +bool +IconCacheEntry::CanConstructBitmap(IconDrawMode mode, icon_size) const +{ + if (mode == kSelected) + // for now only + return true; + + return false; +} + + +bool +IconCacheEntry::HaveIconBitmap(IconDrawMode mode, icon_size size) const +{ + ASSERT(mode == kSelected || mode == kNormalIcon); + // for now only + + if (mode == kNormalIcon) { + if (size == B_MINI_ICON) + return fMiniIcon != NULL; + else + return fLargeIcon != NULL; + } else if (mode == kSelected) { + if (size == B_MINI_ICON) + return fHilitedMiniIcon != NULL; + else + return fHilitedLargeIcon != NULL; + } + return false; +} + + +BBitmap * +IconCacheEntry::IconForMode(IconDrawMode mode, icon_size size) const +{ + ASSERT(mode == kSelected || mode == kNormalIcon); + // for now only + + if (mode == kNormalIcon) { + if (size == B_MINI_ICON) + return fMiniIcon; + else + return fLargeIcon; + } else if (mode == kSelected) { + if (size == B_MINI_ICON) + return fHilitedMiniIcon; + else + return fHilitedLargeIcon; + } + return NULL; +} + + +bool +IconCacheEntry::IconHitTest(BPoint where, IconDrawMode mode, icon_size size) const +{ + ASSERT(where.x < size && where.y < size); + BBitmap *bitmap = IconForMode(mode, size); + if (!bitmap) + return false; + + uchar *bits = (uchar *)bitmap->Bits(); + ASSERT(bits); + return *(bits + (int32)(floor(where.y) * size + where.x)) != B_TRANSPARENT_8_BIT; +} + + +BBitmap * +IconCacheEntry::ConstructBitmap(BBitmap *constructFrom, IconDrawMode requestedMode, + IconDrawMode constructFromMode, icon_size size, LazyBitmapAllocator *lazyBitmap) +{ + ASSERT(requestedMode == kSelected && constructFromMode == kNormalIcon); + // for now + if (requestedMode == kSelected && constructFromMode == kNormalIcon) + return IconCache::sIconCache->MakeSelectedIcon(constructFrom, size, lazyBitmap); + + return NULL; +} + + +BBitmap * +IconCacheEntry::ConstructBitmap(IconDrawMode requestedMode, icon_size size, + LazyBitmapAllocator *lazyBitmap) +{ + BBitmap *source = (size == B_MINI_ICON) ? fMiniIcon : fLargeIcon; + ASSERT(source); + return ConstructBitmap(source, requestedMode, kNormalIcon, size, lazyBitmap); +} + + +bool +IconCacheEntry::AlternateModeForIconConstructing(IconDrawMode requestedMode, + IconDrawMode &alternate, icon_size) +{ + if (requestedMode & kSelected) { + // for now + alternate = kNormalIcon; + return true; + } + return false; +} + + +void +IconCacheEntry::SetIcon(BBitmap *bitmap, IconDrawMode mode, icon_size size, + bool /*create*/) +{ + if (mode == kNormalIcon) { + if (size == B_LARGE_ICON) + fLargeIcon = bitmap; + else + fMiniIcon = bitmap; + } else if (mode == kSelectedIcon) { + if (size == B_LARGE_ICON) + fHilitedLargeIcon = bitmap; + else + fHilitedMiniIcon = bitmap; + } else + TRESPASS(); +} + + +IconCache::IconCache() + : fInitHiliteTable(true) +{ + InitHiliteTable(); +} + + +// The following calls use the icon lookup sequence node-prefered app for node- +// metamime-preferred app for metamime to find an icon; +// if we are trying to get a specialized icon, we will first look for a normal +// icon in each of the locations, if we get a hit, we look for the specialized, +// if we don't find one, we try to auto-construct one, if we can't we assume the +// icon is not available +// for now the code only looks for normal icons, selected icons are auto-generated + +IconCacheEntry * +IconCache::GetIconForPreferredApp(const char *fileTypeSignature, + const char *preferredApp, IconDrawMode mode, icon_size size, + LazyBitmapAllocator *lazyBitmap, IconCacheEntry *entry) +{ + ASSERT(fSharedCache.IsLocked()); + + if (!preferredApp[0]) + return NULL; + + if (!entry) { + entry = fSharedCache.FindItem(fileTypeSignature, preferredApp); + if (entry) { + entry = entry->ResolveIfAlias(&fSharedCache, entry); +#if xDEBUG + PRINT(("File %s; Line %d # looking for %s, type %s, found %x\n", + __FILE__, __LINE__, preferredApp, fileTypeSignature, entry)); +#endif + if (entry->HaveIconBitmap(mode, size)) + return entry; + } + } + + if (!entry || !entry->HaveIconBitmap(NORMAL_ICON_ONLY, size)) { + + PRINT_DISK_HITS(("File %s; Line %d # hitting disk for preferredApp %s, type %s\n", + __FILE__, __LINE__, preferredApp, fileTypeSignature)); + + BMimeType preferredAppType(preferredApp); + BString signature(fileTypeSignature); + signature.ToLower(); + if (preferredAppType.GetIconForType(signature.String(), lazyBitmap->Get(), + size) != B_OK) + return NULL; + + BBitmap *bitmap = lazyBitmap->Adopt(); + if (!entry) { + PRINT_ADD_ITEM(("File %s; Line %d # adding entry for preferredApp %s, type %s\n", + __FILE__, __LINE__, preferredApp, fileTypeSignature)); + entry = fSharedCache.AddItem(fileTypeSignature, preferredApp); + } + entry->SetIcon(bitmap, kNormalIcon, size); + } + + if (mode != kNormalIcon + && entry->HaveIconBitmap(NORMAL_ICON_ONLY, size)) { + entry->ConstructBitmap(mode, size, lazyBitmap); + entry->SetIcon(lazyBitmap->Adopt(), mode, size); + } + + return entry; +} + + +IconCacheEntry * +IconCache::GetIconFromMetaMime(const char *fileType, IconDrawMode mode, + icon_size size, LazyBitmapAllocator *lazyBitmap, IconCacheEntry *entry) +{ + if (!entry) + entry = fSharedCache.FindItem(fileType); + + if (entry) { + entry = entry->ResolveIfAlias(&fSharedCache, entry); + // metamime defines an icon and we have it cached + if (entry->HaveIconBitmap(mode, size)) + return entry; + } + + if (!entry || !entry->HaveIconBitmap(NORMAL_ICON_ONLY, size)) { + PRINT_DISK_HITS(("File %s; Line %d # hitting disk for metamime %s\n", + __FILE__, __LINE__, fileType)); + + BMimeType mime(fileType); + char preferredAppSig[B_MIME_TYPE_LENGTH]; + if (mime.GetPreferredApp(preferredAppSig) == B_OK) { + SharedCacheEntry *aliasTo = 0; + if (entry) + aliasTo = (SharedCacheEntry *)entry->ResolveIfAlias(&fSharedCache); + // look for icon defined by preferred app from metamime + aliasTo = (SharedCacheEntry *)GetIconForPreferredApp(fileType, + preferredAppSig, mode, size, lazyBitmap, aliasTo); + + if (aliasTo) { + // make an aliased entry so that the next time we get a + // hit on the first FindItem in here + if (!entry) { + PRINT_ADD_ITEM(("File %s; Line %d # adding entry as alias for type %s\n", + __FILE__, __LINE__, fileType)); + entry = fSharedCache.AddItem(&aliasTo, fileType); + entry->SetAliasFor(&fSharedCache, aliasTo); + } + ASSERT(aliasTo->HaveIconBitmap(mode, size)); + return aliasTo; + } + } + + // try getting the icon directly from the metamime + if (mime.GetIcon(lazyBitmap->Get(), size) != B_OK) + return NULL; + + BBitmap *bitmap = lazyBitmap->Adopt(); + if (!entry) { + PRINT_ADD_ITEM(("File %s; Line %d # adding entry for type %s\n", + __FILE__, __LINE__, fileType)); + entry = fSharedCache.AddItem(fileType); + } + entry->SetIcon(bitmap, kNormalIcon, size); + } + + ASSERT(entry); + if (mode != kNormalIcon + && entry->HaveIconBitmap(NORMAL_ICON_ONLY, size)) { + entry->ConstructBitmap(mode, size, lazyBitmap); + entry->SetIcon(lazyBitmap->Adopt(), mode, size); + } + +#if xDEBUG + if (!entry->HaveIconBitmap(mode, size)) + PRINT(("failing on %s, mode %ld, size %ld\n", fileType, mode, size)); +#endif + + ASSERT(entry->HaveIconBitmap(mode, size)); + return entry; +} + + +IconCacheEntry * +IconCache::GetIconFromFileTypes(ModelNodeLazyOpener *modelOpener, + IconSource &source, IconDrawMode mode, icon_size size, + LazyBitmapAllocator *lazyBitmap, IconCacheEntry *entry) +{ + ASSERT(fSharedCache.IsLocked()); + // use file types to get the icon + Model *model = modelOpener->TargetModel(); + + const char *fileType = model->MimeType(); + const char *nodePreferredApp = model->PreferredAppSignature(); + if (source == kUnknownSource || source == kUnknownNotFromNode + || source == kPreferredAppForNode) { + + if (nodePreferredApp[0]) { + // file has a locally set preferred app, try getting an icon from + // there + entry = GetIconForPreferredApp(fileType, nodePreferredApp, mode, + size, lazyBitmap, entry); +#if xDEBUG + PRINT(("File %s; Line %d # looking for %s, type %s, found %x\n", + __FILE__, __LINE__, nodePreferredApp, fileType, entry)); +#endif + if (entry) { + source = kPreferredAppForNode; + ASSERT(entry->HaveIconBitmap(mode, size)); + return entry; + } + } + if (source == kPreferredAppForNode) + source = kUnknownSource; + } + + entry = GetIconFromMetaMime(fileType, mode, size, lazyBitmap, entry); + if (!entry) { + // Try getting a supertype handler icon + BMimeType mime(fileType); + if (!mime.IsSupertypeOnly()) { + BMimeType superType; + mime.GetSupertype(&superType); + const char *superTypeFileType = superType.Type(); + if (superTypeFileType) + entry = GetIconFromMetaMime(superTypeFileType, mode, size, + lazyBitmap, entry); +#if DEBUG + else + PRINT(("File %s; Line %d # failed to get supertype for type %s\n", + __FILE__, __LINE__, fileType)); +#endif + } + } + ASSERT(!entry || entry->HaveIconBitmap(mode, size)); + if (entry) { + if (nodePreferredApp[0]) { + // we got a miss using GetIconForPreferredApp before, cache this + // fileType/preferredApp combo with an aliased entry + + // make an aliased entry so that the next time we get a + // hit and substitute a generic icon right away + + PRINT_ADD_ITEM(("File %s; Line %d # adding entry as alias for preferredApp %s, type %s\n", + __FILE__, __LINE__, nodePreferredApp, fileType)); + IconCacheEntry *aliasedEntry = fSharedCache.AddItem((SharedCacheEntry **)&entry, + fileType, nodePreferredApp); + aliasedEntry->SetAliasFor(&fSharedCache, (SharedCacheEntry *)entry); + // OK to cast here, have a runtime check + source = kPreferredAppForNode; + // set source as preferred for node, so that next time we get a hit in + // the initial find that uses GetIconForPreferredApp + } else + source = kMetaMime; +#if DEBUG + if (!entry->HaveIconBitmap(mode, size)) + model->PrintToStream(); +#endif + ASSERT(entry->HaveIconBitmap(mode, size)); + } + return entry; +} + +IconCacheEntry * +IconCache::GetVolumeIcon(AutoLock *nodeCacheLocker, + AutoLock *sharedCacheLocker, + AutoLock **resultingOpenCache, + Model *model, IconSource &source, + IconDrawMode mode, icon_size size, LazyBitmapAllocator *lazyBitmap) +{ + *resultingOpenCache = nodeCacheLocker; + (*resultingOpenCache)->Lock(); + + IconCacheEntry *entry = 0; + if (source != kUnknownSource) { + // cached in the node cache + entry = fNodeCache.FindItem(model->NodeRef()); + if (entry) { + entry = IconCacheEntry::ResolveIfAlias(&fSharedCache, entry); + + if (source == kTrackerDefault) { + // if tracker default, resolved entry is from shared cache + // this could be done a little cleaner if entry had a way to reach + // the cache it is in + *resultingOpenCache = sharedCacheLocker; + (*resultingOpenCache)->Lock(); + } + + if (entry->HaveIconBitmap(mode, size)) + return entry; + } + } + + // try getting using the BVolume::GetIcon call; if miss, + // go for the default mime based icon + if (!entry || !entry->HaveIconBitmap(NORMAL_ICON_ONLY, size)) { + BVolume volume(model->NodeRef()->device); + + if (volume.IsShared()) { + // Check if it's a network share and give it a special icon + BBitmap *bitmap = lazyBitmap->Get(); + GetTrackerResources()->GetIconResource(kResShareIcon, size, bitmap); + if (!entry) { + PRINT_ADD_ITEM(("File %s; Line %d # adding entry for model %s\n", + __FILE__, __LINE__, model->Name())); + entry = fNodeCache.AddItem(model->NodeRef()); + } + entry->SetIcon(lazyBitmap->Adopt(), kNormalIcon, size); + } else if (volume.GetIcon(lazyBitmap->Get(), size) == B_OK) { + // Ask the device for an icon + BBitmap *bitmap = lazyBitmap->Adopt(); + ASSERT(bitmap); + if (!entry) { + PRINT_ADD_ITEM(("File %s; Line %d # adding entry for model %s\n", + __FILE__, __LINE__, model->Name())); + entry = fNodeCache.AddItem(model->NodeRef()); + } + ASSERT(entry); + entry->SetIcon(bitmap, kNormalIcon, size); + source = kVolume; + } else { + // If the volume doesnt have a device it should have the generic icon + entry = GetIconFromMetaMime(B_VOLUME_MIMETYPE, mode, + size, lazyBitmap, entry); + } + } + + if (mode != kNormalIcon + && entry->HaveIconBitmap(NORMAL_ICON_ONLY, size)) { + entry->ConstructBitmap(mode, size, lazyBitmap); + entry->SetIcon(lazyBitmap->Adopt(), mode, size); + } + return entry; +} + + +IconCacheEntry * +IconCache::GetRootIcon(AutoLock *, + AutoLock *sharedCacheLocker, + AutoLock **resultingOpenCache, + Model *, IconSource &source, IconDrawMode mode, + icon_size size, LazyBitmapAllocator *lazyBitmap) +{ + *resultingOpenCache = sharedCacheLocker; + (*resultingOpenCache)->Lock(); + + source = kTrackerSupplied; + return GetIconFromMetaMime(B_ROOT_MIMETYPE, mode, size, lazyBitmap, 0); +} + + +IconCacheEntry * +IconCache::GetWellKnownIcon(AutoLock *, + AutoLock *sharedCacheLocker, + AutoLock **resultingOpenCache, + Model *model, IconSource &source, IconDrawMode mode, icon_size size, + LazyBitmapAllocator *lazyBitmap) +{ + const WellKnowEntryList::WellKnownEntry *wellKnownEntry + = WellKnowEntryList::MatchEntry(model->NodeRef()); + + if (!wellKnownEntry) + return NULL; + + + IconCacheEntry *entry = NULL; + + BString type("tracker/active_"); + type += wellKnownEntry->name; + + *resultingOpenCache = sharedCacheLocker; + (*resultingOpenCache)->Lock(); + + source = kTrackerSupplied; + + entry = fSharedCache.FindItem(type.String()); + if (entry) { + entry = entry->ResolveIfAlias(&fSharedCache, entry); + if (entry->HaveIconBitmap(mode, size)) + return entry; + } + + if (!entry || !entry->HaveIconBitmap(NORMAL_ICON_ONLY, size)) { + + // match up well known entries in the file system with specialized + // icons stored in Tracker's resources + int32 resid = -1; + switch (wellKnownEntry->which) { + case B_BOOT_DISK: + resid = kResBootVolumeIcon; + break; + + case B_BEOS_DIRECTORY: + resid = kResBeosFolderIcon; + break; + + case B_USER_DIRECTORY: + resid = kResHomeDirIcon; + break; + + case B_BEOS_FONTS_DIRECTORY: + case B_COMMON_FONTS_DIRECTORY: + case B_USER_FONTS_DIRECTORY: + resid = kResFontDirIcon; + break; + + case B_BEOS_APPS_DIRECTORY: + case B_APPS_DIRECTORY: + case B_USER_DESKBAR_APPS_DIRECTORY: + resid = kResAppsDirIcon; + break; + + case B_BEOS_PREFERENCES_DIRECTORY: + case B_PREFERENCES_DIRECTORY: + case B_USER_DESKBAR_PREFERENCES_DIRECTORY: + resid = kResPrefsDirIcon; + break; + + case B_USER_MAIL_DIRECTORY: + resid = kResMailDirIcon; + break; + + case B_USER_QUERIES_DIRECTORY: + resid = kResQueryDirIcon; + break; + + case B_COMMON_DEVELOP_DIRECTORY: + case B_USER_DESKBAR_DEVELOP_DIRECTORY: + resid = kResDevelopDirIcon; + break; + + case B_USER_CONFIG_DIRECTORY: + resid = kResConfigDirIcon; + break; + + case B_USER_PEOPLE_DIRECTORY: + resid = kResPersonDirIcon; + break; + + case B_USER_DOWNLOADS_DIRECTORY: + resid = kResDownloadDirIcon; + break; + + default: + return NULL; + } + + entry = fSharedCache.AddItem(type.String()); + + BBitmap *bitmap = lazyBitmap->Get(); + GetTrackerResources()->GetIconResource(resid, size, bitmap); + entry->SetIcon(lazyBitmap->Adopt(), kNormalIcon, size); + + } + + if (mode != kNormalIcon + && entry->HaveIconBitmap(NORMAL_ICON_ONLY, size)) { + entry->ConstructBitmap(mode, size, lazyBitmap); + entry->SetIcon(lazyBitmap->Adopt(), mode, size); + } + + ASSERT(entry->HaveIconBitmap(mode, size)); + return entry; +} + + +IconCacheEntry * +IconCache::GetNodeIcon(ModelNodeLazyOpener *modelOpener, + AutoLock *nodeCacheLocker, + AutoLock **resultingOpenCache, + Model *model, IconSource &source, + IconDrawMode mode, icon_size size, + LazyBitmapAllocator *lazyBitmap, IconCacheEntry *entry, bool permanent) +{ + *resultingOpenCache = nodeCacheLocker; + (*resultingOpenCache)->Lock(); + + entry = fNodeCache.FindItem(model->NodeRef()); + if (!entry || !entry->HaveIconBitmap(NORMAL_ICON_ONLY, size)) { + modelOpener->OpenNode(); + + BFile *file = NULL; + + // if we are dealing with an application, use the BAppFileInfo + // superset of node; this makes GetIcon grab the proper icon for + // an app + if (model->IsExecutable()) + file = dynamic_cast(model->Node()); + + PRINT_DISK_HITS(("File %s; Line %d # hitting disk for node %s\n", + __FILE__, __LINE__, model->Name())); + + status_t result; + if (file) + result = GetAppIconFromAttr(file, lazyBitmap->Get(), size); + else + result = GetFileIconFromAttr(model->Node(), lazyBitmap->Get(), size); + + if (result == B_OK) { + // node has it's own icon, use it + + BBitmap *bitmap = lazyBitmap->Adopt(); + PRINT_ADD_ITEM(("File %s; Line %d # adding entry for model %s\n", + __FILE__, __LINE__, model->Name())); + entry = fNodeCache.AddItem(model->NodeRef(), permanent); + ASSERT(entry); + entry->SetIcon(bitmap, kNormalIcon, size); + if (mode != kNormalIcon) { + entry->ConstructBitmap(mode, size, lazyBitmap); + entry->SetIcon(lazyBitmap->Adopt(), mode, size); + } + source = kNode; + } + } + + if (!entry) { + (*resultingOpenCache)->Unlock(); + *resultingOpenCache = NULL; + } else if (!entry->HaveIconBitmap(mode, size) + && entry->HaveIconBitmap(NORMAL_ICON_ONLY, size)) { + entry->ConstructBitmap(mode, size, lazyBitmap); + entry->SetIcon(lazyBitmap->Adopt(), mode, size); + ASSERT(entry->HaveIconBitmap(mode, size)); + } + + return entry; +} + + +IconCacheEntry * +IconCache::GetGenericIcon(AutoLock *sharedCacheLocker, + AutoLock **resultingOpenCache, + Model *model, IconSource &source, + IconDrawMode mode, icon_size size, + LazyBitmapAllocator *lazyBitmap, IconCacheEntry *entry) +{ + *resultingOpenCache = sharedCacheLocker; + (*resultingOpenCache)->Lock(); + + entry = GetIconFromMetaMime(B_FILE_MIMETYPE, mode, + size, lazyBitmap, 0); + + if (!entry) + return NULL; + + // make an aliased entry so that the next time we get a + // hit and substitute a generic icon right away + PRINT_ADD_ITEM(("File %s; Line %d # adding entry for preferredApp %s, type %s\n", + __FILE__, __LINE__, model->PreferredAppSignature(), + model->MimeType())); + IconCacheEntry *aliasedEntry = fSharedCache.AddItem( + (SharedCacheEntry **)&entry, model->MimeType(), + model->PreferredAppSignature()); + aliasedEntry->SetAliasFor(&fSharedCache, (SharedCacheEntry *)entry); + + source = kMetaMime; + + ASSERT(entry->HaveIconBitmap(mode, size)); + return entry; +} + + +IconCacheEntry * +IconCache::GetFallbackIcon(AutoLock *sharedCacheLocker, + AutoLock **resultingOpenCache, + Model *model, IconDrawMode mode, icon_size size, + LazyBitmapAllocator *lazyBitmap, IconCacheEntry *entry) +{ + *resultingOpenCache = sharedCacheLocker; + (*resultingOpenCache)->Lock(); + + entry = fSharedCache.AddItem(model->MimeType(), + model->PreferredAppSignature()); + + BBitmap *bitmap = lazyBitmap->Get(); + GetTrackerResources()->GetIconResource(kResFileIcon, size, bitmap); + entry->SetIcon(lazyBitmap->Adopt(), kNormalIcon, size); + + if (mode != kNormalIcon) { + entry->ConstructBitmap(mode, size, lazyBitmap); + entry->SetIcon(lazyBitmap->Adopt(), mode, size); + } + + ASSERT(entry->HaveIconBitmap(mode, size)); + return entry; +} + + +IconCacheEntry * +IconCache::Preload(AutoLock *nodeCacheLocker, + AutoLock *sharedCacheLocker, + AutoLock **resultingCache, + Model *model, IconDrawMode mode, icon_size size, + bool permanent) +{ + IconCacheEntry *entry = NULL; + + AutoLock *resultingOpenCache = NULL; + // resultingOpenCache is the locker that points to the cache that + // ended with a hit and will be used for the drawing + + { // scope for modelOpener + + ModelNodeLazyOpener modelOpener(model); + // this opener takes care of opening the model and possibly + // closing it when we are done + LazyBitmapAllocator lazyBitmap(size); + // lazyBitmap manages bitmap allocation and freeing if needed + + IconSource source = model->IconFrom(); + if (source == kUnknownSource || source == kUnknownNotFromNode) { + + // fish for special first models and handle them appropriately + if (model->IsVolume()) { + // volume may use specialized icon in the volume node + entry = GetNodeIcon(&modelOpener, nodeCacheLocker, + &resultingOpenCache, model, source, mode, size, + &lazyBitmap, entry, permanent); + + if (!entry || !entry->HaveIconBitmap(mode, size)) + // look for volume defined icon + entry = GetVolumeIcon(nodeCacheLocker, sharedCacheLocker, + &resultingOpenCache, model, source, mode, + size, &lazyBitmap); + + } else if (model->IsRoot()) { + + entry = GetRootIcon(nodeCacheLocker, sharedCacheLocker, + &resultingOpenCache, model, source, mode, size, &lazyBitmap); + ASSERT(entry); + + } else { + if (source == kUnknownSource) + // look for node icons first + entry = GetNodeIcon(&modelOpener, nodeCacheLocker, + &resultingOpenCache, model, source, + mode, size, &lazyBitmap, entry, permanent); + + + if (!entry) { + // no node icon, look for file type based one + modelOpener.OpenNode(); + // use file types to get the icon + resultingOpenCache = sharedCacheLocker; + resultingOpenCache->Lock(); + + entry = GetIconFromFileTypes(&modelOpener, source, mode, size, + &lazyBitmap, 0); + + if (!entry) // we don't have an icon, go with the generic + entry = GetGenericIcon(sharedCacheLocker, &resultingOpenCache, + model, source, mode, size, &lazyBitmap, entry); + } + } + // update the icon source + model->SetIconFrom(source); + + } else { + // we already know where the icon should come from, + // use shortcuts to get it + switch (source) { + case kNode: + resultingOpenCache = nodeCacheLocker; + resultingOpenCache->Lock(); + + entry = GetNodeIcon(&modelOpener, nodeCacheLocker, + &resultingOpenCache, model, source, mode, + size, &lazyBitmap, entry, permanent); + + if (entry) { + entry = IconCacheEntry::ResolveIfAlias(&fSharedCache, entry); + if (!entry->HaveIconBitmap(mode, size) + && entry->HaveIconBitmap(NORMAL_ICON_ONLY, size)) { + entry->ConstructBitmap(mode, size, &lazyBitmap); + entry->SetIcon(lazyBitmap.Adopt(), mode, size); + } + ASSERT(entry->HaveIconBitmap(mode, size)); + } + break; + case kTrackerSupplied: + if (model->IsRoot()) { + entry = GetRootIcon(nodeCacheLocker, sharedCacheLocker, + &resultingOpenCache, model, source, mode, size, + &lazyBitmap); + break; + } else { + entry = GetWellKnownIcon(nodeCacheLocker, sharedCacheLocker, + &resultingOpenCache, model, source, mode, size, + &lazyBitmap); + + if (entry) + break; + } + // fall through + case kTrackerDefault: + case kVolume: + if (model->IsVolume()) { + entry = GetNodeIcon(&modelOpener, nodeCacheLocker, + &resultingOpenCache, model, source, + mode, size, &lazyBitmap, entry, permanent); + if (!entry || !entry->HaveIconBitmap(mode, size)) + entry = GetVolumeIcon(nodeCacheLocker, sharedCacheLocker, + &resultingOpenCache, model, source, mode, size, + &lazyBitmap); + break; + } + // fall through + case kMetaMime: + case kPreferredAppForType: + case kPreferredAppForNode: + resultingOpenCache = sharedCacheLocker; + resultingOpenCache->Lock(); + + entry = GetIconFromFileTypes(&modelOpener, source, mode, size, + &lazyBitmap, 0); + ASSERT(!entry || entry->HaveIconBitmap(mode, size)); + + if (!entry || !entry->HaveIconBitmap(mode, size)) + // we don't have an icon, go with the generic + entry = GetGenericIcon(sharedCacheLocker, &resultingOpenCache, + model, source, mode, size, &lazyBitmap, entry); + + model->SetIconFrom(source); + // the source shouldn't change in this case; if it does though we + // might never be hitting the correct icon and instead keep leaking + // entries after each miss + // this now happens if an app defines an icon but a GetIconForType + // fails and we fall back to generic icon + // ToDo: + // fix this and add an assert to the effect + + ASSERT(entry); + ASSERT(entry->HaveIconBitmap(mode, size)); + break; + + default: + TRESPASS(); + } + } + + if (!entry || !entry->HaveIconBitmap(mode, size)) { + // we don't have an icon, go with the generic + PRINT(("icon cache complete miss, falling back on generic icon for %s\n", + model->Name())); + entry = GetGenericIcon(sharedCacheLocker, &resultingOpenCache, + model, source, mode, size, &lazyBitmap, entry); + + // we don't even have generic, something is really broken, + // go with hardcoded generic icon + if (!entry || !entry->HaveIconBitmap(mode, size)) { + PRINT(("icon cache complete miss, falling back on generic icon for %s\n", + model->Name())); + entry = GetFallbackIcon(sharedCacheLocker, &resultingOpenCache, + model, mode, size, &lazyBitmap, entry); + } + + // force icon pick up next time around because we probably just + // hit a node in transition + model->SetIconFrom(kUnknownSource); + } + } + + ASSERT(entry && entry->HaveIconBitmap(mode, size)); + + if (resultingCache) + *resultingCache = resultingOpenCache; + + return entry; +} + + +void +IconCache::Draw(Model *model, BView *view, BPoint where, IconDrawMode mode, + icon_size size, bool async) +{ + // the following does not actually lock the caches, we are using the + // lockLater mode; we will decide which of the two to lock down depending + // on where we get the icon from + AutoLock nodeCacheLocker(&fNodeCache, false); + AutoLock sharedCacheLocker(&fSharedCache, false); + + AutoLock *resultingCacheLocker; + IconCacheEntry *entry = Preload(&nodeCacheLocker, &sharedCacheLocker, + &resultingCacheLocker, model, mode, size, false); + // Preload finds/creates the appropriate entry, locking down the + // cache it is in and returns the whole state back to here + + if (!entry) + return; + + ASSERT(entry); + ASSERT(entry->HaveIconBitmap(mode, size)); + // got the entry, now draw it + resultingCacheLocker->LockedItem()->Draw(entry, view, where, mode, + size, async); + + // either of the two cache lockers that got locked down by this call get + // unlocked at this point +} + + +void +IconCache::SyncDraw(Model *model, BView *view, BPoint where, IconDrawMode mode, + icon_size size, void (*blitFunc)(BView *, BPoint, BBitmap *, void *), + void *passThruState) +{ + AutoLock nodeCacheLocker(&fNodeCache, false); + AutoLock sharedCacheLocker(&fSharedCache, false); + + AutoLock *resultingCacheLocker; + IconCacheEntry *entry = Preload(&nodeCacheLocker, &sharedCacheLocker, + &resultingCacheLocker, model, mode, size, false); + + if (!entry) + return; + + ASSERT(entry); + ASSERT(entry->HaveIconBitmap(mode, size)); + resultingCacheLocker->LockedItem()->Draw(entry, view, where, + mode, size, blitFunc, passThruState); +} + + +void +IconCache::Preload(Model *model, IconDrawMode mode, icon_size size, bool permanent) +{ + AutoLock nodeCacheLocker(&fNodeCache, false); + AutoLock sharedCacheLocker(&fSharedCache, false); + + Preload(&nodeCacheLocker, &sharedCacheLocker, 0, model, mode, size, permanent); +} + + +status_t +IconCache::Preload(const char *fileType, IconDrawMode mode, icon_size size) +{ + AutoLock sharedCacheLocker(&fSharedCache); + LazyBitmapAllocator lazyBitmap(size); + + BMimeType mime(fileType); + char preferredAppSig[B_MIME_TYPE_LENGTH]; + status_t result = mime.GetPreferredApp(preferredAppSig); + if (result != B_OK) + return result; + + // try getting the icon from the preferred app for the signature + IconCacheEntry *entry = GetIconForPreferredApp(fileType, preferredAppSig, + mode, size, &lazyBitmap, 0); + if (entry) + return B_OK; + + // try getting the icon directly from the metamime + result = mime.GetIcon(lazyBitmap.Get(), size); + + if (result != B_OK) + return result; + + entry = fSharedCache.AddItem(fileType); + BBitmap *bitmap = lazyBitmap.Adopt(); + entry->SetIcon(bitmap, kNormalIcon, size); + if (mode != kNormalIcon) { + entry->ConstructBitmap(mode, size, &lazyBitmap); + entry->SetIcon(lazyBitmap.Adopt(), mode, size); + } + + return B_OK; +} + + +void +IconCache::Deleting(const Model *model) +{ + AutoLock lock(&fNodeCache); + + if (model->IconFrom() == kNode) + fNodeCache.Deleting(model->NodeRef()); + + // don't care if the node uses the shared cache +} + + +void +IconCache::Removing(const Model *model) +{ + AutoLock lock(&fNodeCache); + + if (model->IconFrom() == kNode) + fNodeCache.Removing(model->NodeRef()); +} + + +void +IconCache::Deleting(const BView *view) +{ + AutoLock lock(&fNodeCache); + fNodeCache.Deleting(view); +} + + +void +IconCache::IconChanged(Model *model) +{ + AutoLock lock(&fNodeCache); + + if (model->IconFrom() == kNode || model->IconFrom() == kVolume) + fNodeCache.Deleting(model->NodeRef()); + + model->ResetIconFrom(); +} + + +void +IconCache::IconChanged(const char *mimeType, const char *appSignature) +{ + AutoLock sharedLock(&fSharedCache); + SharedCacheEntry *entry = fSharedCache.FindItem(mimeType, appSignature); + if (!entry) + return; + + AutoLock nodeLock(&fNodeCache); + + entry = (SharedCacheEntry *)fSharedCache.ResolveIfAlias(entry); + ASSERT(entry); + int32 index = fSharedCache.EntryIndex(entry); + + fNodeCache.RemoveAliasesTo(index); + fSharedCache.RemoveAliasesTo(index); + + fSharedCache.IconChanged(entry); +} + + +BBitmap * +IconCache::MakeSelectedIcon(const BBitmap *normal, icon_size size, + LazyBitmapAllocator *lazyBitmap) +{ + return MakeTransformedIcon(normal, size, fHiliteTable, lazyBitmap); +} + +#if xDEBUG + +static void +DumpBitmap(const BBitmap *bitmap) +{ + if (!bitmap){ + printf("NULL bitmap passed to DumpBitmap\n"); + return; + } + int32 length = bitmap->BitsLength(); + + printf("data length %ld \n", length); + + int32 columns = (int32)bitmap->Bounds().Width() + 1; + const unsigned char *bitPtr = (const unsigned char *)bitmap->Bits(); + for (; length >= 0; length--) { + for (int32 columnIndex = 0; columnIndex < columns; + columnIndex++, length--) + printf("%c%c", "0123456789ABCDEF"[(*bitPtr)/0x10], + "0123456789ABCDEF"[(*bitPtr++)%0x10]); + + printf("\n"); + } + printf("\n"); +} + +#endif + +void +IconCache::InitHiliteTable() +{ + // build the color transform tables for different icon modes + BScreen screen(B_MAIN_SCREEN_ID); + rgb_color color; + for (int32 index = 0; index < kColorTransformTableSize; index++) { + color = screen.ColorForIndex((uchar)index); + fHiliteTable[index] = screen.IndexForColor(tint_color(color, 1.3f)); + } + + fHiliteTable[B_TRANSPARENT_8_BIT] = B_TRANSPARENT_8_BIT; + fInitHiliteTable = false; +} + + +BBitmap * +IconCache::MakeTransformedIcon(const BBitmap *src, icon_size /*size*/, + int32 colorTransformTable[], LazyBitmapAllocator *lazyBitmap) +{ + if (fInitHiliteTable) + InitHiliteTable(); + + BBitmap *result = lazyBitmap->Get(); + int32 bitsLength = result->BitsLength(); + // Do I need a SetBits here? could just copy/transform straight from src + result->SetBits(src->Bits(), bitsLength, 0, kDefaultIconDepth); + + uchar *bits = (uchar *)result->Bits(); + for (int32 index = 0; index < bitsLength; index++) + bits[index] = (uchar)colorTransformTable[(uchar)bits[index]]; + + return result; +} + + +bool +IconCache::IconHitTest(BPoint where, const Model *model, IconDrawMode mode, + icon_size size) +{ + AutoLock nodeCacheLocker(&fNodeCache, false); + AutoLock sharedCacheLocker(&fSharedCache, false); + + AutoLock *resultingCacheLocker; + IconCacheEntry *entry = Preload(&nodeCacheLocker, &sharedCacheLocker, + &resultingCacheLocker, const_cast(model), mode, size, false); + // Preload finds/creates the appropriate entry, locking down the + // cache it is in and returns the whole state back to here + + if (entry) + return entry->IconHitTest(where, mode, size); + + return false; +} + + +void +IconCacheEntry::RetireIcons(BObjectList *retiredBitmapList) +{ + if (fLargeIcon) { + retiredBitmapList->AddItem(fLargeIcon); + fLargeIcon = NULL; + } + if (fMiniIcon) { + retiredBitmapList->AddItem(fMiniIcon); + fMiniIcon = NULL; + } + if (fHilitedLargeIcon) { + retiredBitmapList->AddItem(fHilitedLargeIcon); + fHilitedLargeIcon = NULL; + } + if (fHilitedMiniIcon) { + retiredBitmapList->AddItem(fHilitedMiniIcon); + fHilitedMiniIcon = NULL; + } + + int32 count = retiredBitmapList->CountItems(); + if (count > 10 * 1024) { + PRINT(("nuking old icons from the retired bitmap list\n")); + for (count = 512; count > 0; count--) + delete retiredBitmapList->RemoveItemAt(0); + } +} + + +// #pragma mark - + + +// In debug mode keep the hash table sizes small so that they grow a lot and +// execercise the resizing code a lot. In release mode allocate them large up-front +// for better performance + +SharedIconCache::SharedIconCache() +#if DEBUG + : SimpleIconCache("Shared Icon cache aka \"The Dead-Locker\""), + fHashTable(20), + fElementArray(20), + fRetiredBitmaps(20, true) +#else + : SimpleIconCache("Tracker shared icon cache"), + fHashTable(1000), + fElementArray(1024), + fRetiredBitmaps(256, true) +#endif +{ + fHashTable.SetElementVector(&fElementArray); +} + + +void +SharedIconCache::Draw(IconCacheEntry *entry, BView *view, BPoint where, + IconDrawMode mode, icon_size size, bool async) +{ + ((SharedCacheEntry *)entry)->Draw(view, where, mode, size, async); +} + + +void +SharedIconCache::Draw(IconCacheEntry *entry, BView *view, BPoint where, + IconDrawMode mode, icon_size size, void (*blitFunc)(BView *, BPoint, + BBitmap *, void *), void *passThruState) +{ + ((SharedCacheEntry *)entry)->Draw(view, where, mode, size, + blitFunc, passThruState); +} + + +SharedCacheEntry * +SharedIconCache::FindItem(const char *fileType, const char *appSignature) const +{ + ASSERT(fileType); + if (!fileType) + fileType = B_FILE_MIMETYPE; + + SharedCacheEntry *result = fHashTable.FindFirst(SharedCacheEntry::Hash(fileType, + appSignature)); + + if (!result) + return NULL; + + for(;;) { + if (result->fFileType == fileType && result->fAppSignature == appSignature) + return result; + + if (result->fNext < 0) + break; + + result = const_cast(&fElementArray.At(result->fNext)); + } + + return NULL; +} + + +SharedCacheEntry * +SharedIconCache::AddItem(const char *fileType, const char *appSignature) +{ + ASSERT(fileType); + if (!fileType) + fileType = B_FILE_MIMETYPE; + + SharedCacheEntry *result = &fHashTable.Add(SharedCacheEntry::Hash(fileType, + appSignature)); + result->SetTo(fileType, appSignature); + return result; +} + + +SharedCacheEntry * +SharedIconCache::AddItem(SharedCacheEntry **outstandingEntry, const char *fileType, + const char *appSignature) +{ + int32 entryToken = fHashTable.ElementIndex(*outstandingEntry); + ASSERT(entryToken >= 0); + + ASSERT(fileType); + if (!fileType) + fileType = B_FILE_MIMETYPE; + + SharedCacheEntry *result = &fHashTable.Add(SharedCacheEntry::Hash(fileType, + appSignature)); + result->SetTo(fileType, appSignature); + *outstandingEntry = fHashTable.ElementAt(entryToken); + + return result; +} + + +void +SharedIconCache::IconChanged(SharedCacheEntry *entry) +{ + // by now there should be no aliases to entry, just remove entry + // itself + ASSERT(entry->fAliasForIndex == -1); + entry->RetireIcons(&fRetiredBitmaps); + fHashTable.Remove(entry); +} + + +void +SharedIconCache::RemoveAliasesTo(int32 aliasIndex) +{ + int32 count = fHashTable.VectorSize(); + for (int32 index = 0; index < count; index++) { + SharedCacheEntry *entry = fHashTable.ElementAt(index); + if (entry->fAliasForIndex == aliasIndex) + fHashTable.Remove(entry); + } +} + + +void +SharedIconCache::SetAliasFor(IconCacheEntry *alias, const SharedCacheEntry *original) const +{ + alias->fAliasForIndex = fHashTable.ElementIndex(original); +} + + +SharedCacheEntry::SharedCacheEntry() + : fNext(-1) +{ +} + + +SharedCacheEntry::SharedCacheEntry(const char *fileType, const char *appSignature) + : fNext(-1), + fFileType(fileType), + fAppSignature(appSignature) +{ +} + + +void +SharedCacheEntry::Draw(BView *view, BPoint where, IconDrawMode mode, icon_size size, + bool async) +{ + BBitmap *bitmap = IconForMode(mode, size); + ASSERT(bitmap); + if (async) + view->DrawBitmapAsync(bitmap, where); + else + view->DrawBitmap(bitmap, where); +} + + +void +SharedCacheEntry::Draw(BView *view, BPoint where, IconDrawMode mode, icon_size size, + void (*blitFunc)(BView *, BPoint ,BBitmap *, void *), void *passThruState) +{ + BBitmap *bitmap = IconForMode(mode, size); + ASSERT(bitmap); + (blitFunc)(view, where, bitmap, passThruState); +} + + +uint32 +SharedCacheEntry::Hash(const char *fileType, const char *appSignature) +{ + uint32 hash = HashString(fileType, 0); + if (appSignature && appSignature[0]) + hash = HashString(appSignature, hash); + + return hash; +} + + +uint32 +SharedCacheEntry::Hash() const +{ + uint32 hash = HashString(fFileType.String(), 0); + if (fAppSignature.Length()) + hash = HashString(fAppSignature.String(), hash); + + return hash; +} + + +bool +SharedCacheEntry::operator==(const SharedCacheEntry &entry) const +{ + return fFileType == entry.FileType() && fAppSignature == entry.AppSignature(); +} + + +void +SharedCacheEntry::SetTo(const char *fileType, const char *appSignature) +{ + fFileType = fileType; + fAppSignature = appSignature; +} + + +SharedCacheEntryArray::SharedCacheEntryArray(int32 initialSize) + : OpenHashElementArray(initialSize) +{ +} + + +SharedCacheEntry * +SharedCacheEntryArray::Add() +{ + return &At(OpenHashElementArray::Add()); +} + + +// #pragma mark - + + +NodeCacheEntry::NodeCacheEntry(bool permanent) + : fNext(-1), + fPermanent(permanent) +{ +} + + +NodeCacheEntry::NodeCacheEntry(const node_ref *node, bool permanent) + : fNext(-1), + fRef(*node), + fPermanent(permanent) +{ +} + + +void +NodeCacheEntry::Draw(BView *view, BPoint where, IconDrawMode mode, icon_size size, + bool async) +{ + BBitmap *bitmap = IconForMode(mode, size); + if (false && async) { + TRESPASS(); + // need to copy the bits first in here + view->DrawBitmapAsync(bitmap, where); + } else + view->DrawBitmap(bitmap, where); +} + + +void +NodeCacheEntry::Draw(BView *view, BPoint where, IconDrawMode mode, icon_size size, + void (*blitFunc)(BView *, BPoint ,BBitmap *, void *), void *passThruState) +{ + BBitmap *bitmap = IconForMode(mode, size); + (blitFunc)(view, where, bitmap, passThruState); +} + + +const node_ref * +NodeCacheEntry::Node() const +{ + return &fRef; +} + + +uint32 +NodeCacheEntry::Hash() const +{ + return Hash(&fRef); +} + + +uint32 +NodeCacheEntry::Hash(const node_ref *node) +{ + struct hasher { + uint32 int1; + uint32 int2; + uint32 int3; + } *tmp; + + STATIC_ASSERT(sizeof(hasher) == sizeof(node_ref)); + + tmp = (hasher *)node; + return tmp->int1 ^ tmp->int2 ^ tmp->int3; +} + + +bool +NodeCacheEntry::operator==(const NodeCacheEntry &entry) const +{ + return fRef == entry.fRef; +} + + +void +NodeCacheEntry::SetTo(const node_ref *node) +{ + fRef = *node; +} + + +bool +NodeCacheEntry::Permanent() const +{ + return fPermanent; +} + + +void +NodeCacheEntry::MakePermanent() +{ + fPermanent = true; +} + + +// #pragma mark - + + +NodeIconCache::NodeIconCache() +#if DEBUG + : SimpleIconCache("Node Icon cache aka \"The Dead-Locker\""), + fHashTable(20), + fElementArray(20) +#else + : SimpleIconCache("Tracker node icon cache"), + fHashTable(100), + fElementArray(100) +#endif +{ + fHashTable.SetElementVector(&fElementArray); +} + + +void +NodeIconCache::Draw(IconCacheEntry *entry, BView *view, BPoint where, + IconDrawMode mode, icon_size size, bool async) +{ + + ((NodeCacheEntry *)entry)->Draw(view, where, mode, size, async); +} + + +void +NodeIconCache::Draw(IconCacheEntry *entry, BView *view, BPoint where, + IconDrawMode mode, icon_size size, void (*blitFunc)(BView *, BPoint, + BBitmap *, void *), void *passThruState) +{ + ((NodeCacheEntry *)entry)->Draw(view, where, mode, size, + blitFunc, passThruState); +} + + +NodeCacheEntry * +NodeIconCache::FindItem(const node_ref *node) const +{ + NodeCacheEntry *result = fHashTable.FindFirst(NodeCacheEntry::Hash(node)); + + if (!result) + return NULL; + + for(;;) { + if (*result->Node() == *node) + return result; + + if (result->fNext < 0) + break; + + result = const_cast(&fElementArray.At(result->fNext)); + } + + return NULL; +} + + +NodeCacheEntry * +NodeIconCache::AddItem(const node_ref *node, bool permanent) +{ + NodeCacheEntry *result = &fHashTable.Add(NodeCacheEntry::Hash(node)); + result->SetTo(node); + if (permanent) + result->MakePermanent(); + + return result; +} + + +NodeCacheEntry * +NodeIconCache::AddItem(NodeCacheEntry **outstandingEntry, const node_ref *node) +{ + int32 entryToken = fHashTable.ElementIndex(*outstandingEntry); + + NodeCacheEntry *result = &fHashTable.Add(NodeCacheEntry::Hash(node)); + result->SetTo(node); + *outstandingEntry = fHashTable.ElementAt(entryToken); + + return result; +} + + +void +NodeIconCache::Deleting(const node_ref *node) +{ + NodeCacheEntry *entry = FindItem(node); + ASSERT(entry); + if (!entry || entry->Permanent()) + return; + + fHashTable.Remove(entry); +} + + +void +NodeIconCache::Removing(const node_ref *node) +{ + NodeCacheEntry *entry = FindItem(node); + ASSERT(entry); + if (!entry) + return; + + fHashTable.Remove(entry); +} + + +void +NodeIconCache::Deleting(const BView *) +{ +#ifdef NODE_CACHE_ASYNC_DRAWS + TRESPASS(); +#endif +} + + +void +NodeIconCache::IconChanged(const Model *model) +{ + Deleting(model->NodeRef()); +} + + +void +NodeIconCache::RemoveAliasesTo(int32 aliasIndex) +{ + int32 count = fHashTable.VectorSize(); + for (int32 index = 0; index < count; index++) { + NodeCacheEntry *entry = fHashTable.ElementAt(index); + if (entry->fAliasForIndex == aliasIndex) + fHashTable.Remove(entry); + } +} + + +// #pragma mark - + + +NodeCacheEntryArray::NodeCacheEntryArray(int32 initialSize) + : OpenHashElementArray(initialSize) +{ +} + + +NodeCacheEntry * +NodeCacheEntryArray::Add() +{ + return &At(OpenHashElementArray::Add()); +} + + +// #pragma mark - + + +SimpleIconCache::SimpleIconCache(const char *name) + : fLock(name) +{ +} + + +void +SimpleIconCache::Draw(IconCacheEntry *, BView *, BPoint, IconDrawMode , + icon_size , bool ) +{ + TRESPASS(); + // pure virtual, do nothing +} + + +void +SimpleIconCache::Draw(IconCacheEntry *, BView *, BPoint, IconDrawMode, icon_size, + void(*)(BView *, BPoint, BBitmap *, void *), void *) +{ + TRESPASS(); + // pure virtual, do nothing +} + + +bool +SimpleIconCache::Lock() +{ + return fLock.Lock(); +} + + +void +SimpleIconCache::Unlock() +{ + fLock.Unlock(); +} + + +bool +SimpleIconCache::IsLocked() const +{ + return fLock.IsLocked(); +} + + +// #pragma mark - + + +LazyBitmapAllocator::LazyBitmapAllocator(icon_size size, color_space colorSpace, + bool preallocate) + : fBitmap(NULL), + fSize(size), + fColorSpace(colorSpace) +{ + if (preallocate) + Get(); +} + + +LazyBitmapAllocator::~LazyBitmapAllocator() +{ + delete fBitmap; +} + + +BBitmap * +LazyBitmapAllocator::Get() +{ + if (!fBitmap) + fBitmap = new BBitmap(BRect(0, 0, fSize - 1, fSize - 1), fColorSpace); + + return fBitmap; +} + + +BBitmap * +LazyBitmapAllocator::Adopt() +{ + if (!fBitmap) + Get(); + + BBitmap *result = fBitmap; + fBitmap = NULL; + return result; +} + + +IconCache *IconCache::sIconCache; diff --git a/src/kits/tracker/IconCache.h b/src/kits/tracker/IconCache.h new file mode 100644 index 0000000000..89b86a7122 --- /dev/null +++ b/src/kits/tracker/IconCache.h @@ -0,0 +1,502 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// Icon cache is used for drawing node icons; it caches icons +// and reuses them for successive draws + +#ifndef __NU_ICON_CACHE__ +#define __NU_ICON_CACHE__ + +#include +#include +#include + +#include "AutoLock.h" +#include "ObjectList.h" +#include "OpenHashTable.h" +#include "Utilities.h" + +// Icon cache splits icons into two caches - the shared cache, likely to get the +// most hits and the node cache. Every icon that is found in a mime based +// structure goes into the shared cache, only files that have their own private +// icon use the node cache; +// Entries are only deleted from the shared cache if an icon for a mime type changes, +// this makes async icon drawing easier. Node cache deletes it's entries whenever a +// file gets deleted. + +// if a view ever uses the cache to draw in async mode, it needs to call +// it when it is being destroyed + +namespace BPrivate { + +class Model; +class ModelNodeLazyOpener; +class LazyBitmapAllocator; +class SharedIconCache; +class SharedCacheEntry; + +enum IconDrawMode { + // Difrent states of icon drawing + kSelected = 0x01, + kNotFocused = 0x02, // Tracker window + kOpen = 0x04, // open folder, trash + kNotEmpty = 0x08, // full trash + kDisabled = 0x10, // inactive nav menu entry + kActive = 0x20, // active home dir, boot volume + kLink = 0x40, // symbolic link + kTrackerSpecialized = 0x80, + + // some common combinations + kNormalIcon = 0, + kSelectedIcon = kSelected, + kSelectedInBackgroundIcon = kSelected | kNotFocused, + kOpenIcon = kOpen, + kOpenSelectedIcon = kSelected | kOpen, + kOpenSelectedInBackgroundIcon = kSelected | kNotFocused | kOpen, + kFullIcon = kNotEmpty, + kFullSelectedIcon = kNotEmpty | kOpen, + kDimmedIcon +}; + +#define NORMAL_ICON_ONLY kNormalIcon + // replace use of these defines with mode once the respective getters + // can get non-plain icons + + +// Where did an icon come from +enum IconSource { + kUnknownSource, + kUnknownNotFromNode, // icon origin not known but determined not to be from + // the node itself + kTrackerDefault, // file has no type, Tracker provides generic, folder, + // symlink or app + kTrackerSupplied, // home directory, boot volume, trash, etc. + kMetaMime, // from BMimeType + kPreferredAppForType, // have a preferred application for a type, has an icon + kPreferredAppForNode, // have a preferred application for this node, + // has an icon + kVolume, + kNode +}; + +class IconCacheEntry { + // aliased entries don't own their icons, just point + // to some other entry that does + + // This is used for icons that are defined by a preferred app for + // a metamime, types that do not have an icon an get to point to + // generic, etc. + +public: + IconCacheEntry(); + ~IconCacheEntry(); + + void SetAliasFor(const SharedIconCache *, const SharedCacheEntry *); + static IconCacheEntry *ResolveIfAlias(const SharedIconCache *, IconCacheEntry *); + IconCacheEntry *ResolveIfAlias(const SharedIconCache *); + + void SetIcon(BBitmap *bitmap, IconDrawMode mode, icon_size size, + bool create = false); + + bool HaveIconBitmap(IconDrawMode mode, icon_size size) const; + bool CanConstructBitmap(IconDrawMode mode, icon_size size) const; + static bool AlternateModeForIconConstructing(IconDrawMode requestedMode, + IconDrawMode &alternate, icon_size size); + BBitmap *ConstructBitmap(BBitmap *constructFrom, IconDrawMode requestedMode, + IconDrawMode constructFromMode, icon_size size, + LazyBitmapAllocator *); + BBitmap *ConstructBitmap(IconDrawMode requestedMode, icon_size size, + LazyBitmapAllocator *); + // same as above, always uses normal icon as source + + bool IconHitTest(BPoint, IconDrawMode , icon_size) const; + // given a point, returns true if a non-transparent pixel was hit + + void RetireIcons(BObjectList *retiredBitmapList); + // can't just delete icons, they may be still drawing + // async; instead, put them on the retired list and + // only delete the list if it grows too much, way after + // the icon finishes drawing + // + // This could fail if we retire a lot of icons (10 * 1024) + // while we are drawing them, shouldn't be a practical problem + +protected: + + BBitmap *IconForMode(IconDrawMode mode, icon_size size) const; + void SetIconForMode(BBitmap *bitmap, IconDrawMode mode, icon_size size); + + // list of most common icons + BBitmap *fLargeIcon; + BBitmap *fMiniIcon; + BBitmap *fHilitedLargeIcon; + BBitmap *fHilitedMiniIcon; + int32 fAliasForIndex; + + // list of other icon kinds would be added here + + friend class SharedIconCache; + friend class NodeIconCache; +}; + +class SimpleIconCache { +public: + SimpleIconCache(const char *); + virtual ~SimpleIconCache() {} + + virtual void Draw(IconCacheEntry *, BView *, BPoint, IconDrawMode mode, + icon_size size, bool async = false) = 0; + virtual void Draw(IconCacheEntry *, BView *, BPoint , IconDrawMode , + icon_size , void (*)(BView *, BPoint, BBitmap *, void *), void * = NULL) = 0; + + bool Lock(); + void Unlock(); + bool IsLocked() const; + +private: + Benaphore fLock; +}; + +class SharedCacheEntry : public IconCacheEntry { +public: + SharedCacheEntry(); + SharedCacheEntry(const char *fileType, const char *appSignature = 0); + + void Draw(BView *, BPoint, IconDrawMode mode, icon_size size, + bool async = false); + + void Draw(BView *, BPoint , IconDrawMode , icon_size , + void (*)(BView *, BPoint, BBitmap *, void *), void * = NULL); + + const char *FileType() const; + const char *AppSignature() const; + + // hash table support + uint32 Hash() const; + static uint32 Hash(const char *fileType, const char *appSignature = 0); + bool operator==(const SharedCacheEntry &) const; + void SetTo(const char *fileType, const char *appSignature = 0); + + int32 fNext; +private: + BString fFileType; + BString fAppSignature; + + friend class SharedIconCache; +}; + +class SharedCacheEntryArray : public OpenHashElementArray { + // SharedIconCache stores all it's elements in this array +public: + SharedCacheEntryArray(int32 initialSize); + SharedCacheEntry *Add(); +}; + +class SharedIconCache : public SimpleIconCache { + // SharedIconCache is used for icons that come from the mime database +public: + SharedIconCache(); + + virtual void Draw(IconCacheEntry *, BView *, BPoint, IconDrawMode mode, + icon_size size, bool async = false); + virtual void Draw(IconCacheEntry *, BView *, BPoint , IconDrawMode , + icon_size , void (*)(BView *, BPoint, BBitmap *, void *), void * = NULL); + + SharedCacheEntry *FindItem(const char *fileType, const char *appSignature = 0) + const; + SharedCacheEntry *AddItem(const char *fileType, const char *appSignature = 0); + SharedCacheEntry *AddItem(SharedCacheEntry **outstandingEntry, const char *fileType, + const char *appSignature = 0); + // same as previous AddItem, updates the pointer to outstandingEntry, because + // adding to the hash table makes any pending pointer invalid + void IconChanged(SharedCacheEntry *); + + void SetAliasFor(IconCacheEntry *alias, const SharedCacheEntry *original) const; + IconCacheEntry *ResolveIfAlias(IconCacheEntry *entry) const; + int32 EntryIndex(const SharedCacheEntry *entry) const; + + void RemoveAliasesTo(int32 index); + +private: + OpenHashTable fHashTable; + SharedCacheEntryArray fElementArray; + BObjectList fRetiredBitmaps; + // icons are drawn asynchronously, can't just delete them + // right away, instead have to place them onto the retired bitmap list + // and wait for the next sync to delete them +}; + +class NodeCacheEntry : public IconCacheEntry { +public: + NodeCacheEntry(bool permanent = false); + NodeCacheEntry(const node_ref *, bool permanent = false); + void Draw(BView *, BPoint, IconDrawMode mode, icon_size size, + bool async = false); + + void Draw(BView *, BPoint , IconDrawMode , icon_size , + void (*)(BView *, BPoint, BBitmap *, void *), void * = NULL); + + const node_ref *Node() const; + + uint32 Hash() const; + static uint32 Hash(const node_ref *); + bool operator==(const NodeCacheEntry &) const; + void SetTo(const node_ref *); + void MakePermanent(); + bool Permanent() const; + + int32 fNext; +private: + node_ref fRef; + bool fPermanent; + // special cache entry that has to be deleted explicitly + + friend class NodeIconCache; +}; + +class NodeCacheEntryArray : public OpenHashElementArray { + // NodeIconCache stores all it's elements in this array +public: + NodeCacheEntryArray(int32 initialSize); + NodeCacheEntry *Add(); +}; + +class NodeIconCache : public SimpleIconCache { + // NodeIconCache is used for nodes that define their own icon icons +public: + NodeIconCache(); + + virtual void Draw(IconCacheEntry *, BView *, BPoint, IconDrawMode , + icon_size , bool async = false); + + virtual void Draw(IconCacheEntry *, BView *, BPoint , IconDrawMode , + icon_size , void (*)(BView *, BPoint, BBitmap *, void *), void * = 0); + + NodeCacheEntry *FindItem(const node_ref *) const; + NodeCacheEntry *AddItem(const node_ref *, bool permanent = false); + NodeCacheEntry *AddItem(NodeCacheEntry **outstandingEntry, const node_ref *); + // same as previous AddItem, updates the pointer to outstandingEntry, because + // adding to the hash table makes any pending pointer invalid + void Deleting(const node_ref *); + // model for this node is getting deleted (not necessarily the node itself) + void Removing(const node_ref *); + // used by permanent NodeIconCache entries, when an entry gets + // deleted + void Deleting(const BView *); + void IconChanged(const Model *); + + + void RemoveAliasesTo(int32 index); + +private: + OpenHashTable fHashTable; + NodeCacheEntryArray fElementArray; +}; + +const int32 kColorTransformTableSize = 256; + +class IconCache { +public: + IconCache(); + + void Draw(Model *, BView *, BPoint where, IconDrawMode mode, + icon_size size, bool async = false); + // draw an icon for a model, load the icon from the appropriate + // location if not cached already + + void SyncDraw(Model *, BView *, BPoint , IconDrawMode , + icon_size , void (*)(BView *, BPoint, BBitmap *, void *), + void *passThruState = 0); + // draw an icon for a model, load the icon from the appropriate + // location if not cached already; only works for sync draws, + // once the call returns, the bitmap may be deleted + + // preload calls used to ensure successive cache hit for the respective + // icon, used for common tracker types, etc; Not calling these should only + // cause a slowdown + void Preload(Model *, IconDrawMode mode, icon_size size, bool permanent = false); + status_t Preload(const char *mimeType, IconDrawMode mode, icon_size size); + + void Deleting(const Model *); + // hook to manage unloading icons for nodes that are going away + void Removing(const Model *model); + // used by permanent NodeIconCache entries, when an entry gets + // deleted + void Deleting(const BView *); + // hook to manage deleting draw view caches for views that are + // going away + + // icon changed calls, used when a node or a file type has an icon changed + // the icons for the node/file type will be flushed and re-cached during + // the next draw + void IconChanged(Model *); + void IconChanged(const char *mimeType, const char *appSignature); + + bool IsIconFrom(const Model *, const char *mimeType, + const char *appSignature) const; + // called when metamime database changed to figure out which models + // to redraw + + bool IconHitTest(BPoint, const Model *, IconDrawMode , icon_size ); + + // utility calls for building specialized icons + BBitmap *MakeSelectedIcon(const BBitmap *normal, icon_size, + LazyBitmapAllocator *); + + + static bool NeedsDeletionNotification(IconSource); + + static IconCache *sIconCache; + +private: + + // shared calls + IconCacheEntry *Preload(AutoLock *nodeCache, + AutoLock *sharedCache, + AutoLock **resultingLockedCache, + Model *, IconDrawMode mode, icon_size size, bool permanent); + // preload uses lazy locking, returning the cache we decided + // to use to get the icon + // may be null if we don't care + + // shared mime-based icon retrieval calls + IconCacheEntry *GetIconForPreferredApp(const char *mimeTypeSignature, + const char *preferredApp, IconDrawMode mode, icon_size size, + LazyBitmapAllocator *, IconCacheEntry *); + IconCacheEntry *GetIconFromFileTypes(ModelNodeLazyOpener *, IconSource &source, + IconDrawMode mode, icon_size size, LazyBitmapAllocator *, + IconCacheEntry *); + IconCacheEntry *GetIconFromMetaMime(const char *fileType, IconDrawMode mode, + icon_size size, LazyBitmapAllocator *, + IconCacheEntry *); + IconCacheEntry *GetVolumeIcon(AutoLock *nodeCache, + AutoLock *sharedCache, + AutoLock **resultingLockedCache, + Model *, IconSource &, IconDrawMode mode, + icon_size size, LazyBitmapAllocator *); + IconCacheEntry *GetRootIcon(AutoLock *nodeCache, + AutoLock *sharedCache, + AutoLock **resultingLockedCache, + Model *, IconSource &, IconDrawMode mode, + icon_size size, LazyBitmapAllocator *); + IconCacheEntry *GetWellKnownIcon(AutoLock *nodeCache, + AutoLock *sharedCache, + AutoLock **resultingLockedCache, + Model *, IconSource &, IconDrawMode mode, + icon_size size, LazyBitmapAllocator *); + IconCacheEntry *GetNodeIcon(ModelNodeLazyOpener *, + AutoLock *nodeCache, + AutoLock **resultingLockedCache, + Model *, IconSource &, IconDrawMode mode, + icon_size size, LazyBitmapAllocator *, IconCacheEntry *, bool permanent); + IconCacheEntry *GetGenericIcon(AutoLock *sharedCache, + AutoLock **resultingLockedCache, + Model *, IconSource &, IconDrawMode mode, + icon_size size, LazyBitmapAllocator *, IconCacheEntry *); + IconCacheEntry *GetFallbackIcon(AutoLock *sharedCacheLocker, + AutoLock **resultingOpenCache, + Model *model, IconDrawMode mode, icon_size size, + LazyBitmapAllocator *lazyBitmap, IconCacheEntry *entry); + + BBitmap *MakeTransformedIcon(const BBitmap *, icon_size, + int32 colorTransformTable [], LazyBitmapAllocator *); + + NodeIconCache fNodeCache; + SharedIconCache fSharedCache; + + void InitHiliteTable(); + + int32 fHiliteTable[kColorTransformTableSize]; + bool fInitHiliteTable; + // on if we still need to initialize the hilite table +}; + + +class LazyBitmapAllocator { +// Utility class used when we aren't sure that we will keep a bitmap, +// need a bitmap or be able to construct it properly +public: + LazyBitmapAllocator(icon_size size, color_space colorSpace = kDefaultIconDepth, + bool preallocate = false); + ~LazyBitmapAllocator(); + + BBitmap *Get(); + BBitmap *Adopt(); + +private: + BBitmap *fBitmap; + icon_size fSize; + color_space fColorSpace; +}; + +// nothing but inlines after here + +inline const char * +SharedCacheEntry::FileType() const +{ + return fFileType.String(); +} + +inline const char * +SharedCacheEntry::AppSignature() const +{ + return fAppSignature.String(); +} + +inline bool +IconCache::NeedsDeletionNotification(IconSource from) +{ + return from == kNode; +} + +inline IconCacheEntry * +SharedIconCache::ResolveIfAlias(IconCacheEntry *entry) const +{ + if (entry->fAliasForIndex < 0) + return entry; + + return fHashTable.ElementAt(entry->fAliasForIndex); +} + +inline int32 +SharedIconCache::EntryIndex(const SharedCacheEntry *entry) const +{ + return fHashTable.ElementIndex(entry); +} + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/IconMenuItem.cpp b/src/kits/tracker/IconMenuItem.cpp new file mode 100644 index 0000000000..f1f7a6b5c6 --- /dev/null +++ b/src/kits/tracker/IconMenuItem.cpp @@ -0,0 +1,330 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// menu items with small icons. + +#include +#include +#include + +#include "IconCache.h" +#include "IconMenuItem.h" + + +ModelMenuItem::ModelMenuItem(const Model *model, const char *title, + BMessage *message, char shortcut, uint32 modifiers, + bool drawText, bool extraPad) + : BMenuItem(title, message, shortcut, modifiers), + fModel(*model), + fHeightDelta(0), + fDrawText(drawText), + fExtraPad(extraPad) +{ + ThrowOnInitCheckError(&fModel); + // The 'fExtraPad' field is used to when this menu item is added to + // a menubar instead of a menu. Menus and MenuBars space out items + // differently (more space around items in a menu). This class wants + // to be able to space item the same, no matter where they are. The + // fExtraPad field allows for that. + + if (model->IsRoot()) + SetLabel(model->Name()); + + // ModelMenuItem is used in synchronously invoked menus, make sure + // we invoke with a timeout + SetTimeout(kSynchMenuInvokeTimeout); +} + + +ModelMenuItem::ModelMenuItem(const Model *model, BMenu *menu, bool drawText, + bool extraPad) + : BMenuItem(menu), + fModel(*model), + fHeightDelta(0), + fDrawText(drawText), + fExtraPad(extraPad) +{ + ThrowOnInitCheckError(&fModel); + // ModelMenuItem is used in synchronously invoked menus, make sure + // we invoke with a timeout + SetTimeout(kSynchMenuInvokeTimeout); +} + + +ModelMenuItem::~ModelMenuItem() +{ +} + + +status_t +ModelMenuItem::SetEntry(const BEntry *entry) +{ + return fModel.SetTo(entry); +} + + +void +ModelMenuItem::DrawContent() +{ + if (fDrawText) { + BPoint drawPoint(ContentLocation()); + drawPoint.x += 20 + (fExtraPad ? 6 : 0); + if (fHeightDelta > 0) + drawPoint.y += ceil(fHeightDelta / 2); + Menu()->MovePenTo(drawPoint); + _inherited::DrawContent(); + } + DrawIcon(); +} + + +void +ModelMenuItem::Highlight(bool hilited) +{ + _inherited::Highlight(hilited); + DrawIcon(); +} + + +static void +DimmedIconBlitter(BView *view, BPoint where, BBitmap *bitmap, void *) +{ + view->SetDrawingMode(B_OP_BLEND); + view->DrawBitmap(bitmap, where); + view->SetDrawingMode(B_OP_OVER); +} + + +void +ModelMenuItem::DrawIcon() +{ + Menu()->PushState(); + + BPoint where(ContentLocation()); + // center icon with text. + + float deltaHeight = fHeightDelta < 0 ? -fHeightDelta : 0; + where.y += ceil( deltaHeight/2 ); + + if (fExtraPad) + where.x += 6; + + Menu()->SetDrawingMode(B_OP_OVER); + Menu()->SetLowColor(B_TRANSPARENT_32_BIT); + + // draw small icon, synchronously + if (IsEnabled()) + IconCache::sIconCache->Draw(fModel.ResolveIfLink(), Menu(), where, + kNormalIcon, B_MINI_ICON); + else { + // dimmed, for now use a special blitter; icon cache should + // know how to blit one eventually + IconCache::sIconCache->SyncDraw(fModel.ResolveIfLink(), Menu(), where, + kNormalIcon, B_MINI_ICON, DimmedIconBlitter); + } + + Menu()->PopState(); +} + + +void +ModelMenuItem::GetContentSize(float *width, float *height) +{ + _inherited::GetContentSize(width, height); + fHeightDelta = 16 - *height; + if (*height < 16) + *height = 16; + *width = *width + 20 + (fExtraPad ? 18 : 0); +} + + +status_t +ModelMenuItem::Invoke(BMessage *message) +{ + if (!Menu()) + return B_ERROR; + + if (!IsEnabled()) + return B_ERROR; + + if (!message) + message = Message(); + + if (!message) + return B_BAD_VALUE; + + BMessage clone(*message); + clone.AddInt32("index", Menu()->IndexOf(this)); + clone.AddInt64("when", system_time()); + clone.AddPointer("source", this); + + if ((modifiers() & B_OPTION_KEY) == 0) + // if option not held, remove refs to close to prevent closing + // parent window + clone.RemoveData("nodeRefsToClose"); + + return BInvoker::Invoke(&clone); +} + + +// #pragma mark - + + +SpecialModelMenuItem::SpecialModelMenuItem(const Model *model,BMenu *menu) + : ModelMenuItem(model,menu) +{ +} + + +void +SpecialModelMenuItem::DrawContent() +{ + Menu()->PushState(); + + BFont font; + Menu()->GetFont(&font); + font.SetFace(B_ITALIC_FACE); + Menu()->SetFont(&font); + + _inherited::DrawContent(); + Menu()->PopState(); +} + + +// #pragma mark - + + +IconMenuItem::IconMenuItem(const char *label, BMessage *message, BBitmap *icon) + : PositionPassingMenuItem(label, message), + fDeviceIcon(icon) +{ + // IconMenuItem is used in synchronously invoked menus, make sure + // we invoke with a timeout + SetTimeout(kSynchMenuInvokeTimeout); +} + + +IconMenuItem::IconMenuItem(const char *label, BMessage *message, + const BNodeInfo *nodeInfo, icon_size which) + : PositionPassingMenuItem(label, message), + fDeviceIcon(NULL) +{ + if (nodeInfo) { + fDeviceIcon = new BBitmap(BRect(0, 0, which - 1, which - 1), B_COLOR_8_BIT); + if (nodeInfo->GetTrackerIcon(fDeviceIcon, B_MINI_ICON)) { + delete fDeviceIcon; + fDeviceIcon = NULL; + } + } + + // IconMenuItem is used in synchronously invoked menus, make sure + // we invoke with a timeout + SetTimeout(kSynchMenuInvokeTimeout); +} + + +IconMenuItem::IconMenuItem(const char *label, BMessage *message, + const char *iconType, icon_size which) + : PositionPassingMenuItem(label, message), + fDeviceIcon(NULL) +{ + BMimeType mime(iconType); + fDeviceIcon = new BBitmap(BRect(0, 0, which - 1, which - 1), B_COLOR_8_BIT); + + if (mime.GetIcon(fDeviceIcon, which) != B_OK) { + delete fDeviceIcon; + fDeviceIcon = NULL; + } + + // IconMenuItem is used in synchronously invoked menus, make sure + // we invoke with a timeout + SetTimeout(kSynchMenuInvokeTimeout); +} + + +IconMenuItem::IconMenuItem(BMenu *submenu, BMessage *message, + const char *iconType, icon_size which) + : PositionPassingMenuItem(submenu, message), + fDeviceIcon(NULL) +{ + BMimeType mime(iconType); + fDeviceIcon = new BBitmap(BRect(0, 0, which - 1, which - 1), B_COLOR_8_BIT); + + if (mime.GetIcon(fDeviceIcon, which) != B_OK) { + delete fDeviceIcon; + fDeviceIcon = NULL; + } + + // IconMenuItem is used in synchronously invoked menus, make sure + // we invoke with a timeout + SetTimeout(kSynchMenuInvokeTimeout); +} + + +IconMenuItem::~IconMenuItem() +{ + delete fDeviceIcon; +} + + +void +IconMenuItem::GetContentSize(float *width, float *height) +{ + _inherited::GetContentSize(width, height); + *width += 20; + *height += 3; +} + + +void +IconMenuItem::DrawContent() +{ + BPoint drawPoint(ContentLocation()); + drawPoint.x += 20; + Menu()->MovePenTo(drawPoint); + _inherited::DrawContent(); + + BPoint where(ContentLocation()); + where.y = Frame().top; + + if (fDeviceIcon) { + if (IsEnabled()) + Menu()->SetDrawingMode(B_OP_OVER); + else + Menu()->SetDrawingMode(B_OP_BLEND); + + Menu()->DrawBitmapAsync(fDeviceIcon, where); + } +} + diff --git a/src/kits/tracker/IconMenuItem.h b/src/kits/tracker/IconMenuItem.h new file mode 100644 index 0000000000..8313820b03 --- /dev/null +++ b/src/kits/tracker/IconMenuItem.h @@ -0,0 +1,119 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// Menu item class with small icons. + +#ifndef ICON_MENU_ITEM_H +#define ICON_MENU_ITEM_H + +#include +#include "Model.h" +#include "Utilities.h" +class BNodeInfo; + + +namespace BPrivate { + +const bigtime_t kSynchMenuInvokeTimeout = 5000000; + +class IconMenuItem : public PositionPassingMenuItem { + public: + IconMenuItem(const char *, BMessage *, BBitmap *); + IconMenuItem(const char *, BMessage *, const char *iconType, icon_size which); + IconMenuItem(const char *, BMessage *, const BNodeInfo *nodeInfo, icon_size which); + IconMenuItem(BMenu *, BMessage *, const char *iconType, icon_size which); + virtual ~IconMenuItem(); + + virtual void GetContentSize(float *width, float *height); + virtual void DrawContent(); + + private: + BBitmap *fDeviceIcon; + + typedef BMenuItem _inherited; +}; + + +class ModelMenuItem : public BMenuItem { + public: + ModelMenuItem(const Model *, const char *title, BMessage *, char shortcut = '\0', + uint32 modifiers = 0, bool drawText = true, bool extraPad = false); + ModelMenuItem(const Model *, BMenu *, bool drawText = true, bool extraPad = false); + virtual ~ModelMenuItem(); + + virtual status_t SetEntry(const BEntry *); + virtual void DrawContent(); + virtual void Highlight(bool isHighlighted); + virtual void GetContentSize(float *width, float *height); + + const Model *TargetModel() const; + + protected: + virtual status_t Invoke(BMessage * = NULL); + // overriden to support B_OPTION_KEY + + private: + void DrawIcon(); + + Model fModel; + float fHeightDelta; + bool fDrawText; + bool fExtraPad; + + typedef BMenuItem _inherited; +}; + + +inline const Model * +ModelMenuItem::TargetModel() const +{ + return &fModel; +} + + +class SpecialModelMenuItem : public ModelMenuItem { + public: + SpecialModelMenuItem(const Model *model,BMenu *menu); + + virtual void DrawContent(); + + private: + typedef ModelMenuItem _inherited; +}; + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/InfoWindow.cpp b/src/kits/tracker/InfoWindow.cpp new file mode 100644 index 0000000000..f1f482d144 --- /dev/null +++ b/src/kits/tracker/InfoWindow.cpp @@ -0,0 +1,2059 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "Attributes.h" +#include "AutoLock.h" +#include "Commands.h" +#include "FSUtils.h" +#include "IconCache.h" +#include "IconMenuItem.h" +#include "InfoWindow.h" +#include "Model.h" +#include "NavMenu.h" +#include "PoseView.h" +#include "Tracker.h" +#include "WidgetAttributeText.h" + +const float kDrawMargin = 3.0f; +const float kBorderMargin = 15.0f; +const float kBorderWidth = 32.0f; + +// Offsets taken from TAlertView::Draw in BAlert.cpp +const float kIconHorizOffset = 18.0f; +const float kIconVertOffset = 6.0f; + +// The font height's for the two types of information we display +const float kTitleFontHeight = 14.0f; +const float kAttribFontHeight = 10.0f; + +// Amount you have to move the mouse before a drag starts +const float kDragSlop = 3.0f; + +const rgb_color kAttrTitleColor = {0, 0, 0, 255}; +const rgb_color kAttrValueColor = {0, 0, 0, 255}; +const rgb_color kLinkColor = {0, 0, 220, 255}; +const rgb_color kDarkBorderColor = {184, 184, 184, 255}; + +const uint32 kSetPreferredApp = 'setp'; +const uint32 kSelectNewSymTarget = 'snew'; +const uint32 kNewTargetSelected = 'selc'; +const uint32 kRecalculateSize = 'resz'; +const uint32 kSetLinkTarget = 'link'; +const uint32 kPermissionsSelected = 'sepe'; +const uint32 kOpenLinkSource = 'opls'; +const uint32 kOpenLinkTarget = 'oplt'; + +const uint32 kPaneSwitchClosed = 0; +const uint32 kPaneSwitchOpen = 2; + + +static BString & +PrintFloat(BString &result, float number) +{ + char buffer[128]; + sprintf(buffer, "%.1f", number); + result += buffer; + return result; +} + + +static void +OpenParentAndSelectOriginal(const entry_ref *ref) +{ + BEntry entry(ref); + node_ref node; + entry.GetNodeRef(&node); + + BEntry parent; + entry.GetParent(&parent); + entry_ref parentRef; + parent.GetRef(&parentRef); + + BMessage message(B_REFS_RECEIVED); + message.AddRef("refs", &parentRef); + message.AddData("nodeRefToSelect", B_RAW_TYPE, &node, sizeof(node_ref)); + + be_app->PostMessage(&message); +} + + +static BWindow * +OpenToolTipWindow(BRect rect, const char *name, const char *string, BMessenger target, BFont &font, BMessage *message) +{ + BWindow *window = new BWindow(rect, name, B_BORDERED_WINDOW_LOOK, + B_FLOATING_ALL_WINDOW_FEEL, + B_NOT_MOVABLE | B_NOT_CLOSABLE | B_NOT_ZOOMABLE | B_NOT_MINIMIZABLE + | B_NOT_RESIZABLE | B_AVOID_FOCUS | B_NO_WORKSPACE_ACTIVATION + | B_WILL_ACCEPT_FIRST_CLICK | B_ASYNCHRONOUS_CONTROLS); + + TrackingView *trackingView = new TrackingView(window->Bounds(), + string, &font, message); + trackingView->SetTarget(target); + window->AddChild(trackingView); + + window->Sync(); + window->Show(); + + return window; +} + + +// #pragma mark - + + +BInfoWindow::BInfoWindow(Model *model, int32 group_index, LockingList *list) + : BWindow(BInfoWindow::InfoWindowRect(false), + "InfoWindow", B_TITLED_WINDOW, + B_NOT_RESIZABLE | B_NOT_ZOOMABLE, B_CURRENT_WORKSPACE), + fModel(model), + fStopCalc(false), + fIndex(group_index), + fCalcThreadID(-1), + fWindowList(list), + fPermissionsView(NULL), + fFilePanel(NULL), + fFilePanelOpen(false) +{ + SetPulseRate(1000000); // we use pulse to check freebytes on volume + + TTracker::WatchNode(model->NodeRef(), B_WATCH_ALL | B_WATCH_MOUNT, this); + + // window list is Locked by Tracker around this constructor + if (list) + list->AddItem(this); + + AddShortcut('E', 0, new BMessage(kEditItem)); + AddShortcut('O', 0, new BMessage(kOpenSelection)); + AddShortcut('U', 0, new BMessage(kUnmountVolume)); + AddShortcut('P', 0, new BMessage(kPermissionsSelected)); + + Run(); +} + + +BInfoWindow::~BInfoWindow() +{ + // Check to make sure the file panel is destroyed + delete fFilePanel; + delete fModel; +} + + +BRect +BInfoWindow::InfoWindowRect(bool) +{ + return BRect(70, 50, 385, 240); +} + + +void +BInfoWindow::Quit() +{ + stop_watching(this); + + if (fWindowList) { + AutoLock > lock(fWindowList); + fWindowList->RemoveItem(this); + } + + fStopCalc = true; + + // wait until CalcSize thread has terminated before closing window + status_t result; + wait_for_thread(fCalcThreadID, &result); + + _inherited::Quit(); +} + + +bool +BInfoWindow::IsShowing(const node_ref *node) const +{ + return *TargetModel()->NodeRef() == *node; +} + + +void +BInfoWindow::Show() +{ + BModelOpener modelOpener(TargetModel()); + if (TargetModel()->InitCheck() != B_OK) { + Close(); + return; + } + + AutoLock lock(this); + + BRect attrRect(Bounds()); + fAttributeView = new AttributeView(attrRect, TargetModel()); + AddChild(fAttributeView); + + // position window appropriately based on index + BRect windRect(InfoWindowRect(TargetModel()->IsSymLink() || TargetModel()->IsFile())); + if ((fIndex + 2) % 2 == 1) { + windRect.OffsetBy(320, 0); + fIndex--; + } + + windRect.OffsetBy(fIndex * 8, fIndex * 8); + + // make sure window is visible on screen + BScreen screen(this); + if (!windRect.Intersects(screen.Frame())) + windRect.OffsetTo(50, 50); + + MoveTo(windRect.LeftTop()); + ResizeTo(windRect.Width(), windRect.Height()); + + // volume case is handled by view + if (!TargetModel()->IsVolume()) { + if (TargetModel()->IsDirectory()) { + // if this is a folder then spawn thread to calculate size + SetSizeStr("calculating" B_UTF8_ELLIPSIS); + fCalcThreadID = spawn_thread(BInfoWindow::CalcSize, "CalcSize", B_NORMAL_PRIORITY, this); + resume_thread(fCalcThreadID); + } else { + fAttributeView->SetLastSize(TargetModel()->StatBuf()->st_size); + + BString sizeStr; + GetSizeString(sizeStr, fAttributeView->LastSize(), 0); + SetSizeStr(sizeStr.String()); + } + } + + BString buffer; + buffer << TargetModel()->Name() << " info"; + SetTitle(buffer.String()); + + lock.Unlock(); + _inherited::Show(); +} + + +void +BInfoWindow::MessageReceived(BMessage *message) +{ + switch (message->what) { + case kRestoreState: + Show(); + break; + + case kOpenSelection: + { + BMessage refsMessage(B_REFS_RECEIVED); + refsMessage.AddRef("refs", fModel->EntryRef()); + + // add a messenger to the launch message that will be used to + // dispatch scripting calls from apps to the PoseView + refsMessage.AddMessenger("TrackerViewToken", BMessenger(this)); + be_app->PostMessage(&refsMessage); + break; + } + + case kEditItem: + { + BEntry entry(fModel->EntryRef()); + if (ConfirmChangeIfWellKnownDirectory(&entry, "rename")) + fAttributeView->BeginEditingTitle(); + break; + } + + case kIdentifyEntry: + { + bool force = (modifiers() & B_OPTION_KEY) != 0; + BEntry entry; + if (entry.SetTo(fModel->EntryRef(), true) == B_OK) { + BPath path; + if (entry.GetPath(&path) == B_OK) + update_mime_info(path.Path(), true, false, force ? 2 : 1); + } + break; + } + + case kRecalculateSize: + { + fStopCalc = true; + + // Wait until any current CalcSize thread has terminated before + // starting a new one + status_t result; + wait_for_thread(fCalcThreadID, &result); + + // Start recalculating.. + fStopCalc = false; + SetSizeStr("calculating" B_UTF8_ELLIPSIS); + fCalcThreadID = spawn_thread(BInfoWindow::CalcSize, "CalcSize", B_NORMAL_PRIORITY, this); + resume_thread(fCalcThreadID); + + break; + } + + case kSetLinkTarget: + OpenFilePanel(fModel->EntryRef()); + break; + + // An item was dropped into the window + case B_SIMPLE_DATA: + // If we are not a SymLink, just ignore the request + if (!fModel->IsSymLink()) + break; + // supposed to fall through + + // An item was selected from the file panel + case kNewTargetSelected: + { + // Extract the BEntry, and set its full path to the string value + BEntry targetEntry; + entry_ref ref; + BPath path; + + if (message->FindRef("refs", &ref) == B_OK + && targetEntry.SetTo(&ref, true) == B_OK + && targetEntry.Exists()) { + // We now have to re-target the broken symlink. Unfortunately, + // there's no way to change the target of an existing symlink. + // So we have to delete the old one and create a new one. + // First, stop watching the broken node (we don't want this window + // to quit when the node is removed.) + stop_watching(this); + + // Get the parent + BDirectory parent; + BEntry tmpEntry(TargetModel()->EntryRef()); + if (tmpEntry.GetParent(&parent) != B_OK) + break; + + // Preserve the name + BString name(TargetModel()->Name()); + + // Extract path for new target + BEntry target(&ref); + BPath targetPath; + if (target.GetPath(&targetPath) != B_OK) + break; + + // Preserve the original attributes + AttributeStreamMemoryNode memoryNode; + { + BModelOpener opener(TargetModel()); + AttributeStreamFileNode original(TargetModel()->Node()); + memoryNode << original; + } + + // Delete the broken node. + BEntry oldEntry(TargetModel()->EntryRef()); + oldEntry.Remove(); + + // Create new node + BSymLink link; + parent.CreateSymLink(name.String(), targetPath.Path(), &link); + + // Update our Model() + BEntry symEntry(&parent, name.String()); + fModel->SetTo(&symEntry); + + BModelWriteOpener opener(TargetModel()); + + // Copy the attributes back + AttributeStreamFileNode newNode(TargetModel()->Node()); + newNode << memoryNode; + + // Start watching this again + TTracker::WatchNode(TargetModel()->NodeRef(), B_WATCH_ALL | B_WATCH_MOUNT, this); + + // Tell the attribute view about this new model + fAttributeView->ReLinkTargetModel(TargetModel()); + } + break; + } + + case B_CANCEL: + // File panel window has closed + delete fFilePanel; + fFilePanel = NULL; + // It's no longer open + fFilePanelOpen = false; + break; + + case kUnmountVolume: + // Sanity check that this isn't the boot volume + // (The unmount menu item has been disabled in this + // case, but the shortcut is still active) + if (fModel->IsVolume()) { + BVolume boot; + BVolumeRoster().GetBootVolume(&boot); + BVolume volume(fModel->NodeRef()->device); + if (volume != boot) { + dynamic_cast(be_app)->SaveAllPoseLocations(); + + BMessage unmountMessage(kUnmountVolume); + unmountMessage.AddInt32("device_id", volume.Device()); + be_app->PostMessage(&unmountMessage); + } + } + break; + + case kEmptyTrash: + FSEmptyTrash(); + break; + + case B_NODE_MONITOR: + switch (message->FindInt32("opcode")) { + case B_ENTRY_REMOVED: + { + node_ref itemNode; + message->FindInt32("device", &itemNode.device); + message->FindInt64("node", &itemNode.node); + // our window itself may be deleted + if (*TargetModel()->NodeRef() == itemNode) + Close(); + break; + } + + case B_ENTRY_MOVED: + case B_STAT_CHANGED: + case B_ATTR_CHANGED: + fAttributeView->ModelChanged(TargetModel(), message); + // must be called before the FilePermissionView::ModelChanged() + // call, because it changes the model... (bad style!) + + if (fPermissionsView != NULL) + fPermissionsView->ModelChanged(TargetModel()); + break; + + case B_DEVICE_UNMOUNTED: + { + // We were watching a volume that is no longer mounted, + // we might as well quit + node_ref itemNode; + // Only the device information is available + message->FindInt32("device", &itemNode.device); + if (TargetModel()->NodeRef()->device == itemNode.device) + Close(); + + break; + } + + default: + break; + } + break; + + case kPermissionsSelected: + if (fPermissionsView == NULL) { + // Only true on first call. + fPermissionsView = new FilePermissionsView(BRect(kBorderWidth + 1, + fAttributeView->Bounds().bottom, fAttributeView->Bounds().right, + fAttributeView->Bounds().bottom+80), fModel); + + ResizeBy(0, fPermissionsView->Bounds().Height()); + fAttributeView->AddChild(fPermissionsView); + fAttributeView->SetPermissionsSwitchState(kPaneSwitchOpen); + } else if (fPermissionsView->IsHidden()) { + fPermissionsView->ModelChanged(fModel); + fPermissionsView->Show(); + ResizeBy(0, fPermissionsView->Bounds().Height()); + fAttributeView->SetPermissionsSwitchState(kPaneSwitchOpen); + } else { + fPermissionsView->Hide(); + ResizeBy(0, -fPermissionsView->Bounds().Height()); + fAttributeView->SetPermissionsSwitchState(kPaneSwitchClosed); + } + break; + + default: + _inherited::MessageReceived(message); + break; + } +} + + +void +BInfoWindow::GetSizeString(BString &result, off_t size, int32 fileCount) +{ + char numStr[256]; + sprintf(numStr, "%Ld", size); + BString bytes; + + uint32 length = strlen(numStr); + if (length >= 4) { + uint32 charsTillComma = length % 3; + if (charsTillComma == 0) + charsTillComma = 3; + + uint32 numberIndex = 0; + + while (numStr[numberIndex]) { + bytes += numStr[numberIndex++]; + if (--charsTillComma == 0 && numStr[numberIndex]) { + bytes += ','; + charsTillComma = 3; + } + } + } else + bytes = numStr; + + if (size >= kGBSize) + PrintFloat(result, (float)size / kGBSize) << " GB"; + else if (size >= kMBSize) + PrintFloat(result, (float)size / kMBSize) << " MB"; + else if (size >= kKBSize) + result << (int64)(size + kHalfKBSize) / kKBSize << "K"; + else + result << size; + + if (size >= kKBSize) + result << " (" << bytes; + + result << " bytes"; + + if (size >= kKBSize) + result << ")"; + + if (fileCount) + result << " for " << fileCount << " files"; +} + + +int32 +BInfoWindow::CalcSize(void *castToWindow) +{ + BInfoWindow *window = static_cast(castToWindow); + BDirectory dir(window->TargetModel()->EntryRef()); + BDirectory trashDir; + FSGetTrashDir(&trashDir, window->TargetModel()->EntryRef()->device); + if (dir.InitCheck() != B_OK) { + if (window->StopCalc()) + return B_ERROR; + + AutoLock lock(window); + if (!lock) + return B_ERROR; + + window->SetSizeStr("Error calculating folder size."); + + return B_ERROR; + } + + BEntry dirEntry, trashEntry; + dir.GetEntry(&dirEntry); + trashDir.GetEntry(&trashEntry); + + BString sizeString; + + // check if user has asked for trash dir info + if (dirEntry != trashEntry) { + // if not, perform normal info calculations + off_t size = 0; + int32 fileCount = 0; + int32 dirCount = 0; + FSRecursiveCalcSize(window, &dir, &size, &fileCount, &dirCount); + + // got the size value, update the size string + GetSizeString(sizeString, size, fileCount); + } + else { + // in the trash case, iterate through and sum up + // size/counts for all present trash dirs + off_t totalSize = 0, currentSize; + int32 totalFileCount = 0, currentFileCount; + int32 totalDirCount = 0, currentDirCount; + BVolumeRoster volRoster; + volRoster.Rewind(); + BVolume volume; + while (volRoster.GetNextVolume(&volume) == B_OK) { + if (!volume.IsPersistent()) + continue; + + currentSize = 0; + currentFileCount = 0; + currentDirCount = 0; + + BDirectory trashDir; + if (FSGetTrashDir(&trashDir, volume.Device()) == B_OK) { + FSRecursiveCalcSize(window, &trashDir, ¤tSize, + ¤tFileCount, ¤tDirCount); + totalSize += currentSize; + totalFileCount += currentFileCount; + totalDirCount += currentDirCount; + } + } + GetSizeString(sizeString, totalSize, totalFileCount); + } + + if (window->StopCalc()) + // window closed, bail + return B_OK; + + AutoLock lock(window); + if (lock.IsLocked()) + window->SetSizeStr(sizeString.String()); + + return B_OK; +} + + +void +BInfoWindow::SetSizeStr(const char *sizeStr) +{ + AttributeView *view = dynamic_cast(FindView("attr_view")); + if (view) + view->SetSizeStr(sizeStr); +} + + +void +BInfoWindow::OpenFilePanel(const entry_ref *ref) +{ + // Open a file dialog box to allow the user to select a new target + // for the sym link + if (fFilePanel == NULL) { + BMessenger runner(this); + + fFilePanel = new BFilePanel(B_OPEN_PANEL, &runner, ref, + B_FILE_NODE | B_SYMLINK_NODE | B_DIRECTORY_NODE, + false, new BMessage(kNewTargetSelected)); + + if (fFilePanel != NULL) { + fFilePanel->SetButtonLabel(B_DEFAULT_BUTTON,"Select"); + fFilePanel->Window()->ResizeTo(500, 300); + BString title; + title << "Link \"" << fModel->Name() << "\" to:"; + fFilePanel->Window()->SetTitle(title.String()); + fFilePanel->Show(); + fFilePanelOpen = true; + } + } else if (!fFilePanelOpen) { + fFilePanel->Show(); + fFilePanelOpen = true; + } else { + fFilePanelOpen = true; + fFilePanel->Window()->Activate(true); + } +} + + +// #pragma mark - + + +AttributeView::AttributeView(BRect rect, Model *model) + : BView(rect, "attr_view", B_FOLLOW_ALL_SIDES, B_WILL_DRAW | B_PULSE_NEEDED), + fDivider(0), + fPreferredAppMenu(NULL), + fModel(model), + fIconModel(model), + fMouseDown(false), + fDragging(false), + fDoubleClick(false), + fTrackingState(no_track), + fIsDropTarget(false), + fTitleEditView(NULL), + fPathWindow(NULL), + fLinkWindow(NULL), + fDescWindow(NULL) +{ + // Set view color to standard background grey + SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + + // If the model is a symlink, then we deference the model to + // get the targets icon + if (fModel->IsSymLink()) { + Model *resolvedModel = new Model(model->EntryRef(), true, true); + if (resolvedModel->InitCheck() == B_OK) + fIconModel = resolvedModel; + // broken link, just show the symlink + } + + // Create the rect for displaying the icon + fIconRect.Set(0, 0, B_LARGE_ICON - 1, B_LARGE_ICON - 1); + // Offset taken from BAlert + fIconRect.OffsetBy(kIconHorizOffset, kIconVertOffset); + + // The title rect + // The magic numbers are used to properly calculate the rect so that + // when the editing text view is displayed, the position of the text + // does not change. + BFont currentFont; + font_height fontMetrics; + GetFont(¤tFont); + currentFont.SetSize(kTitleFontHeight); + currentFont.GetHeight(&fontMetrics); + + fTitleRect.left = fIconRect.right + 5; + fTitleRect.top = 0; + fTitleRect.bottom = fontMetrics.ascent + 1; + fTitleRect.right = min_c(fTitleRect.left + currentFont.StringWidth(fModel->Name()), Bounds().Width() - 5); + // Offset so that it centers with the icon + fTitleRect.OffsetBy(0, fIconRect.top + ((fIconRect.Height() - fTitleRect.Height()) / 2)); + // Make some room for the border for when we are in edit mode + // (Negative numbers increase the size of the rect) + fTitleRect.InsetBy(-1, -2); + + fFreeBytes = -1; + fSizeStr = ""; + fSizeRect.Set(0, 0, 0, 0); + + // Find offset for attributes, might be overiden below if there + // is a prefered handle menu displayed + currentFont.SetSize(kAttribFontHeight); + fDivider = currentFont.StringWidth("Modified:") + kBorderMargin + kBorderWidth + 1; + // Add a preferred handler pop-up menu if this item + // is a file...This goes in place of the Link To: + // string... + if (model->IsFile()) { + BMimeType mime(fModel->MimeType()); + BNodeInfo nodeInfo(fModel->Node()); + + // But don't add the menu if the file is executable + if (!fModel->IsExecutable()) { + SetFontSize(kAttribFontHeight); + float lineHeight = CurrentFontHeight(); + + BRect preferredAppRect(kBorderWidth + kBorderMargin, + fTitleRect.bottom + (lineHeight * 7), + Bounds().Width() - 5, fTitleRect.bottom + (lineHeight * 8)); + fPreferredAppMenu = new BMenuField(preferredAppRect, "", "", new BPopUpMenu("")); + fDivider = currentFont.StringWidth("Opens with:") + 5; + fPreferredAppMenu->SetDivider(fDivider); + fDivider += (preferredAppRect.left - 2); + fPreferredAppMenu->SetFont(¤tFont); + fPreferredAppMenu->SetHighColor(kAttrTitleColor); + fPreferredAppMenu->SetLabel("Opens with:"); + + char prefSignature[B_MIME_TYPE_LENGTH]; + nodeInfo.GetPreferredApp(prefSignature); + + BMessage supportingAppList; + mime.GetSupportingApps(&supportingAppList); + + // Add the default menu item and set it to marked + BMenuItem *result; + result = new BMenuItem("Default Application", new BMessage(kSetPreferredApp)); + result->SetTarget(this); + fPreferredAppMenu->Menu()->AddItem(result); + result->SetMarked(true); + + for (int32 index = 0; ; index++) { + const char *signature; + if (supportingAppList.FindString("applications", index, &signature) != B_OK) + break; + + // Only add separator item if there are more items + if (index == 0) + fPreferredAppMenu->Menu()->AddSeparatorItem(); + + BMessage *itemMessage = new BMessage(kSetPreferredApp); + itemMessage->AddString("signature", signature); + + status_t err = B_ERROR; + entry_ref entry; + + if (signature && signature[0]) + err = be_roster->FindApp(signature, &entry); + + if (err != B_OK) + result = new BMenuItem(signature, itemMessage); + else + result = new BMenuItem(entry.name, itemMessage); + + result->SetTarget(this); + fPreferredAppMenu->Menu()->AddItem(result); + if (strcmp(signature, prefSignature) == 0) + result->SetMarked(true); + } + + AddChild(fPreferredAppMenu); + } + } + + fPermissionsSwitch = new PaneSwitch(BRect(), "Permissions"); + fPermissionsSwitch->SetMessage(new BMessage(kPermissionsSelected)); + AddChild(fPermissionsSwitch); + + BStringView *stringView = new BStringView(BRect(), "Permissions", "Permissions"); + + AddChild(stringView); + + stringView->ResizeToPreferred(); + + BRect bounds = Bounds(), stringViewBounds = stringView->Bounds(); + fPermissionsSwitch->MoveTo(kBorderWidth + 3, bounds.bottom - stringViewBounds.bottom + + (stringViewBounds.bottom - 11) / 2); + + stringView->MoveTo(kBorderWidth + 11 + 3, bounds.bottom - + stringViewBounds.bottom); + + fPermissionsSwitch->ResizeTo(10, 11); + + InitStrings(model); +} + + +AttributeView::~AttributeView() +{ + if (fPathWindow->Lock()) + fPathWindow->Quit(); + + if (fLinkWindow->Lock()) + fLinkWindow->Quit(); + + if (fDescWindow->Lock()) + fDescWindow->Quit(); + + if (fModel->IsSymLink() && fIconModel != fModel) + delete fIconModel; +} + + +void +AttributeView::InitStrings(const Model *model) +{ + BMimeType mime; + char kind[B_MIME_TYPE_LENGTH]; + + ASSERT(model->IsNodeOpen()); + + BRect drawBounds(Bounds()); + drawBounds.left = fDivider; + + // We'll do our own truncation later on in Draw() + WidgetAttributeText::AttrAsString(model, &fCreatedStr, kAttrStatCreated, + B_TIME_TYPE, drawBounds.Width() - kBorderMargin, this); + WidgetAttributeText::AttrAsString(model, &fModifiedStr, kAttrStatModified, + B_TIME_TYPE, drawBounds.Width() - kBorderMargin, this); + WidgetAttributeText::AttrAsString(model, &fPathStr, kAttrPath, + B_STRING_TYPE, 0, this); + + // Use the same method as used to resolve fIconModel, which handles + // both absolute and relative symlinks. if the link is broken, try to + // get a little more information. + if (model->IsSymLink()) { + bool linked = false; + + Model resolvedModel(model->EntryRef(), true, true); + if (resolvedModel.InitCheck() == B_OK) { + // Get the path of the link + BPath traversedPath; + resolvedModel.GetPath(&traversedPath); + + // If the BPath is initialized, then check the file for existence + if (traversedPath.InitCheck() == B_OK) { + BEntry entry(traversedPath.Path(), false); // look at the target itself + if (entry.InitCheck() == B_OK && entry.Exists()) + linked = true; + } + } + + // always show the target as it is: absolute or relative! + BSymLink symLink(model->EntryRef()); + char linkToPath[B_PATH_NAME_LENGTH]; + symLink.ReadLink(linkToPath, B_PATH_NAME_LENGTH); + fLinkToStr = linkToPath; + if (!linked) + fLinkToStr += " (broken)"; // link points to missing object + } else if (model->IsExecutable()) { + if (((Model*)model)->GetLongVersionString(fDescStr, B_APP_VERSION_KIND) != B_OK) + fDescStr = "-"; + } + + if (mime.SetType(model->MimeType()) == B_OK + && mime.GetShortDescription(kind) == B_OK) + fKindStr = kind; + + if (fKindStr.Length() == 0) + fKindStr = model->MimeType(); +} + + +void +AttributeView::AttachedToWindow() +{ + BFont font(be_plain_font); + + font.SetSpacing(B_BITMAP_SPACING); + SetFont(&font); + + CheckAndSetSize(); + if (fPreferredAppMenu) + fPreferredAppMenu->Menu()->SetTargetForItems(this); + + _inherited::AttachedToWindow(); +} + + +void +AttributeView::Pulse() +{ + CheckAndSetSize(); + _inherited::Pulse(); +} + + +void +AttributeView::ModelChanged(Model *model, BMessage *message) +{ + BRect drawBounds(Bounds()); + drawBounds.left = fDivider; + + switch (message->FindInt32("opcode")) { + case B_ENTRY_MOVED: + { + node_ref dirNode; + node_ref itemNode; + dirNode.device = itemNode.device = message->FindInt32("device"); + message->FindInt64("to directory", &dirNode.node); + message->FindInt64("node", &itemNode.node); + + const char *name; + if (message->FindString("name", &name) != B_OK) + return; + + // ensure notification is for us + if (*model->NodeRef() == itemNode + // For volumes, the device ID is obviously not handled in a + // consistent way; the node monitor sends us the ID of the parent + // device, while the model is set to the device of the volume + // directly - this hack works for volumes that are mounted in + // the root directory + || model->IsVolume() + && itemNode.device == 1 + && itemNode.node == model->NodeRef()->node) { + model->UpdateEntryRef(&dirNode, name); + BString title; + title << name << " info"; + Window()->SetTitle(title.String()); + WidgetAttributeText::AttrAsString(model, &fPathStr, kAttrPath, B_STRING_TYPE, 0, this); + } + break; + } + + case B_STAT_CHANGED: + if (model->OpenNode() == B_OK) { + WidgetAttributeText::AttrAsString(model, &fCreatedStr, + kAttrStatCreated, B_TIME_TYPE, drawBounds.Width() - kBorderMargin, this); + WidgetAttributeText::AttrAsString(model, &fModifiedStr, + kAttrStatModified, B_TIME_TYPE, drawBounds.Width() - kBorderMargin, this); + + // don't change the size if it's a directory + if (!model->IsDirectory()) { + fLastSize = model->StatBuf()->st_size; + fSizeStr = ""; + BInfoWindow::GetSizeString(fSizeStr, fLastSize, 0); + } + model->CloseNode(); + } + break; + + case B_ATTR_CHANGED: + { + // watch for icon updates + const char *attrName; + if (message->FindString("attr", &attrName) == B_OK) { + if (strcmp(attrName, kAttrLargeIcon) == 0) + Invalidate(BRect(10, 10, 10 + B_LARGE_ICON, 10 + B_LARGE_ICON)); + + if (strcmp(attrName, kAttrMIMEType) == 0) { + if (model->OpenNode() == B_OK) { + InitStrings(model); + model->CloseNode(); + } + Invalidate(); + } + } + break; + } + + default: + break; + } + + // Update the icon stuff + if (fIconModel != fModel) { + delete fIconModel; + fIconModel = NULL; + } + + fModel = model; + if (fModel->IsSymLink()) { + // if we are looking at a symlink, deference the model and look at the + // target + Model *resolvedModel = new Model(model->EntryRef(), true, true); + if (resolvedModel->InitCheck() == B_OK) { + if (fIconModel != fModel) + delete fIconModel; + fIconModel = resolvedModel; + } else { + fIconModel = model; + delete resolvedModel; + } + InitStrings(model); + Invalidate(); + } + + drawBounds.left = fDivider; + Invalidate(drawBounds); +} + + +// This only applies to symlinks. If the target of the symlink +// was changed, then we have to update the entire model. +// (Since in order to re-target a symlink, we had to delete +// the old model and create a new one; BSymLink::SetTarget(), +// would be nice) + +void +AttributeView::ReLinkTargetModel(Model *model) +{ + fModel = model; + if (fModel->IsSymLink()) { + Model *resolvedModel = new Model(model->EntryRef(), true, true); + if (resolvedModel->InitCheck() == B_OK) { + if (fIconModel != fModel) + delete fIconModel; + fIconModel = resolvedModel; + } else { + fIconModel = fModel; + delete resolvedModel; + } + } + InitStrings(model); + Invalidate(Bounds()); +} + + +void +AttributeView::MouseDown(BPoint point) +{ + // Make sure this isn't the trash directory + BEntry entry; + fModel->GetEntry(&entry); + + // Assume this isn't part of a double click + fDoubleClick = false; + + // Start tracking the mouse if we are in any of the hotspots + if (fLinkRect.Contains(point)) { + InvertRect(fLinkRect); + fTrackingState = link_track; + } else if (fPathRect.Contains(point)) { + InvertRect(fPathRect); + fTrackingState = path_track; + } else if (fTitleRect.Contains(point)) { + // You can't change the name of the trash + if (!FSIsTrashDir(&entry) + && ConfirmChangeIfWellKnownDirectory(&entry, "rename", true) + && fTitleEditView == 0) + BeginEditingTitle(); + } else if (fTitleEditView) { + FinishEditingTitle(true); + } else if (fSizeRect.Contains(point)) { + if (fModel->IsDirectory() && !fModel->IsVolume()) { + InvertRect(fSizeRect); + fTrackingState = size_track; + } else + fTrackingState = no_track; + } else if (fIconRect.Contains(point)) { + uint32 buttons; + Window()->CurrentMessage()->FindInt32("buttons", (int32 *)&buttons); + if (((modifiers() & B_CONTROL_KEY) != 0) || (buttons & B_SECONDARY_MOUSE_BUTTON) != 0) { + // Show contextual menu + BPopUpMenu *contextMenu = new BPopUpMenu("FileContext", false, false); + BuildContextMenu(contextMenu); + if (contextMenu) { + contextMenu->SetAsyncAutoDestruct(true); + contextMenu->Go(ConvertToScreen(point), true, true, ConvertToScreen(fIconRect)); + } + } else { + // Check to see if the point is actually on part of the icon, + // versus just in the container rect. The icons are always + // the large version + BPoint offsetPoint; + offsetPoint.x = point.x - fIconRect.left; + offsetPoint.y = point.y - fIconRect.top; + if (IconCache::sIconCache->IconHitTest(offsetPoint, fIconModel, kNormalIcon, B_LARGE_ICON)) { + // Can't drag the trash anywhere.. + fTrackingState = FSIsTrashDir(&entry) ? open_only_track : icon_track; + + // Check for possible double click + if (abs((int32)(fClickPoint.x - point.x)) < kDragSlop + && abs((int32)(fClickPoint.y - point.y)) < kDragSlop) { + int32 clickCount; + Window()->CurrentMessage()->FindInt32("clicks", &clickCount); + + // This checks the *previous* click point + if (clickCount == 2) { + offsetPoint.x = fClickPoint.x - fIconRect.left; + offsetPoint.y = fClickPoint.y - fIconRect.top; + fDoubleClick = IconCache::sIconCache->IconHitTest(offsetPoint, + fIconModel, kNormalIcon, B_LARGE_ICON); + } + } + } + } + } + fClickPoint = point; + fMouseDown = true; + SetMouseEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY); +} + + +void +AttributeView::MouseMoved(BPoint point, uint32, const BMessage *message) +{ + // Highlight Drag target + if (message && message->ReturnAddress() != BMessenger(this) + && message->what == B_SIMPLE_DATA + && BPoseView::CanHandleDragSelection(fModel, message, (modifiers() & B_CONTROL_KEY) != 0)) { + bool overTarget = fIconRect.Contains(point); + SetDrawingMode(B_OP_OVER); + if (overTarget != fIsDropTarget) { + IconCache::sIconCache->Draw(fIconModel, this, fIconRect.LeftTop(), + overTarget ? kSelectedIcon : kNormalIcon, B_LARGE_ICON, true); + fIsDropTarget = overTarget; + } + } + + switch (fTrackingState) { + case link_track: + if (fLinkRect.Contains(point) != fMouseDown) { + fMouseDown = !fMouseDown; + InvertRect(fLinkRect); + } + break; + + case path_track: + if (fPathRect.Contains(point) != fMouseDown) { + fMouseDown = !fMouseDown; + InvertRect(fPathRect); + } + break; + + case size_track: + if (fSizeRect.Contains(point) != fMouseDown) { + fMouseDown = !fMouseDown; + InvertRect(fSizeRect); + } + break; + + case icon_track: + if (fMouseDown && !fDragging && (abs((int32)(point.x - fClickPoint.x)) > kDragSlop + || abs((int32)(point.y - fClickPoint.y)) > kDragSlop)) { + // Find the required height + BFont font; + GetFont(&font); + font.SetSize(kAttribFontHeight); + + float height = CurrentFontHeight(kAttribFontHeight) + fIconRect.Height() + 8; + BRect rect(0, 0, min_c(fIconRect.Width() + + font.StringWidth(fModel->Name()) + 4, fIconRect.Width() * 3), height); + BBitmap *dragBitmap = new BBitmap(rect, B_RGBA32, true); + dragBitmap->Lock(); + BView *view = new BView(dragBitmap->Bounds(), "", B_FOLLOW_NONE, 0); + dragBitmap->AddChild(view); + view->SetOrigin(0, 0); + BRect clipRect(view->Bounds()); + BRegion newClip; + newClip.Set(clipRect); + view->ConstrainClippingRegion(&newClip); + + // Transparent draw magic + view->SetHighColor(0, 0, 0, 0); + view->FillRect(view->Bounds()); + view->SetDrawingMode(B_OP_ALPHA); + view->SetHighColor(0, 0, 0, 128); // set the level of transparency by value + view->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_COMPOSITE); + + // Draw the icon + float hIconOffset = (rect.Width() - fIconRect.Width()) / 2; + IconCache::sIconCache->Draw(fIconModel, view, BPoint(hIconOffset, 0), + kNormalIcon, B_LARGE_ICON, true); + + // See if we need to truncate the string + BString nameString(fModel->Name()); + if (view->StringWidth(fModel->Name()) > rect.Width()) + view->TruncateString(&nameString, B_TRUNCATE_END, rect.Width() - 5); + + // Draw the label + font_height fontHeight; + font.GetHeight(&fontHeight); + float leftText = (view->StringWidth(nameString.String()) - fIconRect.Width()) / 2; + view->MovePenTo(BPoint(hIconOffset - leftText + 2, + fIconRect.Height() + (fontHeight.ascent + 2))); + view->DrawString(nameString.String()); + + view->Sync(); + dragBitmap->Unlock(); + + BMessage message(B_REFS_RECEIVED); + message.AddPoint("click_pt", fClickPoint); + BPoint tmpLoc; + uint32 button; + GetMouse(&tmpLoc, &button); + if (button) + message.AddInt32("buttons", (int32)button); + + message.AddInt32("be:actions", + (modifiers() & B_OPTION_KEY) != 0 ? B_COPY_TARGET : B_MOVE_TARGET); + message.AddRef("refs", fModel->EntryRef()); + DragMessage(&message, dragBitmap, B_OP_ALPHA, BPoint((fClickPoint.x - fIconRect.left) + + hIconOffset, fClickPoint.y - fIconRect.top), this); + fDragging = true; + } + break; + + case open_only_track : + // Special type of entry that can't be renamed or drag and dropped + // It can only be opened by double clicking on the icon + break; + + default: + { + // Only consider this if the window is the active window. + // We have to manually get the mouse here in the event that the + // mouse is over a pop-up window + uint32 buttons; + BPoint point; + GetMouse(&point, &buttons); + if (Window()->IsActive() && !buttons) { + // If we are down here, then that means that we're tracking the mouse + // but not from a mouse down. In this case, we're just interested in + // knowing whether or not we need to display the "pop-up" version + // of the path or link text. + BRect screen(BScreen(B_MAIN_SCREEN_ID).Frame()); + BFont font; + GetFont(&font); + if (fPathRect.Contains(point) + && StringWidth(fPathStr.String()) > (Bounds().Width() - (fDivider + kBorderMargin))) { + fTrackingState = no_track; + BRect rect(fPathRect); + rect.right = rect.left + StringWidth(fPathStr.String()) + 4; + rect.OffsetBy(Window()->Frame().left, Window()->Frame().top); + if (rect.left < 0) + rect.OffsetBy(rect.left * -1, 0); + else if (rect.right > screen.right) + rect.OffsetBy(screen.right - rect.right, 0); + + if (!fPathWindow || BMessenger(fPathWindow).IsValid() == false) { + fPathWindow = OpenToolTipWindow(rect, "fPathWindow", fPathStr.String(), + BMessenger(this), font, new BMessage(kOpenLinkSource)); + } + } else if (fLinkRect.Contains(point) + && StringWidth(fLinkToStr.String()) > (Bounds().Width() - (fDivider + kBorderMargin))) { + fTrackingState = no_track; + BRect rect(fLinkRect); + rect.right = rect.left + StringWidth(fLinkToStr.String()) + 4; + rect.OffsetBy(Window()->Frame().left, Window()->Frame().top); + if (rect.left < 0) + rect.OffsetBy(rect.left * -1, 0); + else if (rect.right > screen.right) + rect.OffsetBy(screen.right - rect.right, 0); + + if (!fLinkWindow || BMessenger(fLinkWindow).IsValid() == false) { + fLinkWindow = OpenToolTipWindow(rect, "fLinkWindow", fLinkToStr.String(), + BMessenger(this), font, new BMessage(kOpenLinkTarget)); + } + } else if (fDescRect.Contains(point) + && StringWidth(fDescStr.String()) > (Bounds().Width() - (fDivider + kBorderMargin))) { + fTrackingState = no_track; + BRect rect(fDescRect); + rect.right = rect.left + StringWidth(fDescStr.String()) + 4; + rect.OffsetBy(Window()->Frame().left, Window()->Frame().top); + if (rect.left < 0) + rect.OffsetBy(rect.left * -1, 0); + else if (rect.right > screen.right) + rect.OffsetBy(screen.right - rect.right, 0); + + if (!fDescWindow || BMessenger(fDescWindow).IsValid() == false) { + fDescWindow = OpenToolTipWindow(rect, "fDescWindow", fDescStr.String(), + BMessenger(this), font, NULL); + } + } + } + break; + } + } +} + + +void +AttributeView::OpenLinkSource() +{ + OpenParentAndSelectOriginal(fModel->EntryRef()); +} + + +void +AttributeView::OpenLinkTarget() +{ + Model resolvedModel(fModel->EntryRef(), true, true); + BEntry entry; + if (resolvedModel.InitCheck() == B_OK) { + // Get the path of the link + BPath traversedPath; + resolvedModel.GetPath(&traversedPath); + + // If the BPath is initialized, then check the file for existence + if (traversedPath.InitCheck() == B_OK) + entry.SetTo(traversedPath.Path()); + } + if (entry.InitCheck() != B_OK || !entry.Exists()) { + // Open a file dialog panel to allow the user to relink. + BInfoWindow *window = dynamic_cast(Window()); + if (window) + window->OpenFilePanel(fModel->EntryRef()); + + } else { + entry_ref ref; + entry.GetRef(&ref); + BPath path(&ref); + printf("Opening link target: %s\n", path.Path()); + OpenParentAndSelectOriginal(&ref); + } +} + + +void +AttributeView::MouseUp(BPoint point) +{ + // Are we in the link rect? + if (fTrackingState == link_track && fLinkRect.Contains(point)) { + InvertRect(fLinkRect); + OpenLinkTarget(); + } else if (fTrackingState == path_track && fPathRect.Contains(point)) { + InvertRect(fPathRect); + OpenLinkSource(); + } else if ((fTrackingState == icon_track || fTrackingState == open_only_track) + && fIconRect.Contains(point)) { + // If it was a double click, then tell Tracker to open the item + // The CurrentMessage() here does *not* have a "clicks" field, + // which is why we are tracking the clicks with this temp var + if (fDoubleClick){ + // Double click, launch. + BMessage message(B_REFS_RECEIVED); + message.AddRef("refs", fModel->EntryRef()); + + // add a messenger to the launch message that will be used to + // dispatch scripting calls from apps to the PoseView + message.AddMessenger("TrackerViewToken", BMessenger(this)); + be_app->PostMessage(&message); + fDoubleClick = false; + } + } else if (fTrackingState == size_track && fSizeRect.Contains(point)) { + // Recalculate size + Window()->PostMessage(kRecalculateSize); + } + + // End mouse tracking + fMouseDown = false; + fDragging = false; + fTrackingState = no_track; + +} + + +void +AttributeView::CheckAndSetSize() +{ + if (fModel->IsVolume()) { + BVolume volume(fModel->NodeRef()->device); + off_t freeBytes = volume.FreeBytes(); + if (fFreeBytes == freeBytes) + return; + + fFreeBytes = freeBytes; + off_t capacity = volume.Capacity(); + char buffer[500]; + if (capacity >= kGBSize) + sprintf(buffer, "%.1f G", (float)capacity / kGBSize); + else + sprintf(buffer, "%.1f M", (float)capacity / kMBSize); + + sprintf(buffer + strlen(buffer), "B (%.1f MB used -- %.1f MB free)", + (float)(capacity - fFreeBytes) / kMBSize, + (float)fFreeBytes / kMBSize); + + fSizeStr = buffer; + } else if (fModel->IsFile()) { + // poll for size changes because they do not get node monitored + // until a file gets closed (with the old BFS) + StatStruct statBuf; + BModelOpener opener(fModel); + + if (fModel->InitCheck() != B_OK || fModel->Node()->GetStat(&statBuf) != B_OK) + return; + + if (fLastSize == statBuf.st_size) + return; + + fLastSize = statBuf.st_size; + fSizeStr = ""; + BInfoWindow::GetSizeString(fSizeStr, fLastSize, 0); + } else + return; + + BRect bounds(Bounds()); + float lineHeight = CurrentFontHeight() + 2; + bounds.Set(fDivider, fIconRect.bottom, bounds.right, fIconRect.bottom + lineHeight); + Invalidate(bounds); +} + + +void +AttributeView::MessageReceived(BMessage *message) +{ + if (message->WasDropped() + && message->what == B_SIMPLE_DATA + && message->ReturnAddress() != BMessenger(this) + && fIconRect.Contains(ConvertFromScreen(message->DropPoint())) + && BPoseView::CanHandleDragSelection(fModel, message, (modifiers() & B_CONTROL_KEY) != 0)) { + BPoseView::HandleDropCommon(message, fModel, 0, this, message->DropPoint()); + Invalidate(fIconRect); + return; + } + + switch (message->what) { + case kSetPreferredApp: + { + BNode node(fModel->EntryRef()); + BNodeInfo nodeInfo(&node); + + const char *newSignature; + if (message->FindString("signature", &newSignature) != B_OK) + newSignature = NULL; + + fModel->SetPreferredAppSignature(newSignature); + nodeInfo.SetPreferredApp(newSignature); + + break; + } + + case kOpenLinkSource: + OpenLinkSource(); + break; + + case kOpenLinkTarget: + OpenLinkTarget(); + break; + + default: + _inherited::MessageReceived(message); + } +} + + +void +AttributeView::Draw(BRect) +{ + // Set the low color for anti-aliasing + SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + + // Clear the old contents + SetHighColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + FillRect(Bounds()); + + // Draw the dark grey area on the left + BRect drawBounds(Bounds()); + drawBounds.right = kBorderWidth; + SetHighColor(kDarkBorderColor); + FillRect(drawBounds); + + // Draw the icon, straddling the border + SetDrawingMode(B_OP_OVER); + IconCache::sIconCache->Draw(fIconModel, this, fIconRect.LeftTop(), + kNormalIcon, B_LARGE_ICON, true); + + // Font information + font_height fontMetrics; + BFont currentFont; + float lineHeight = 0; + float lineBase = 0; + // Draw the main title if the user is not currently editing it + if (fTitleEditView == NULL) { + SetFont(be_bold_font); + SetFontSize(kTitleFontHeight); + GetFont(¤tFont); + currentFont.GetHeight(&fontMetrics); + lineHeight = CurrentFontHeight() + 5; + lineBase = fTitleRect.bottom - fontMetrics.descent; + SetHighColor(kAttrValueColor); + MovePenTo(BPoint(fIconRect.right + 6, lineBase)); + + // Recalculate the rect width + fTitleRect.right = min_c(fTitleRect.left + currentFont.StringWidth(fModel->Name()), + Bounds().Width() - 5); + // Check for possible need of truncation + if (StringWidth(fModel->Name()) > fTitleRect.Width()) { + BString nameString(fModel->Name()); + TruncateString(&nameString, B_TRUNCATE_END, + fTitleRect.Width() - 2); + DrawString(nameString.String()); + } else + DrawString(fModel->Name()); + } + + // Draw the attribute font stuff + SetFont(be_plain_font); + SetFontSize(kAttribFontHeight); + GetFontHeight(&fontMetrics); + lineHeight = CurrentFontHeight() + 5; + + // Starting base line for the first string + lineBase = fTitleRect.bottom + lineHeight; + + // Capacity/size + SetHighColor(kAttrTitleColor); + if (fModel->IsVolume()) { + MovePenTo(BPoint(fDivider - (StringWidth("Capacity:")), lineBase)); + DrawString("Capacity:"); + } else { + MovePenTo(BPoint(fDivider - (StringWidth("Size:")), lineBase)); + fSizeRect.left = fDivider + 2; + fSizeRect.top = lineBase - fontMetrics.ascent; + fSizeRect.bottom = lineBase + fontMetrics.descent; + DrawString("Size:"); + } + + MovePenTo(BPoint(fDivider + kDrawMargin, lineBase)); + SetHighColor(kAttrValueColor); + // Check for possible need of truncation + if (StringWidth(fSizeStr.String()) > (Bounds().Width() - (fDivider + kBorderMargin))) { + BString tmpString(fSizeStr.String()); + TruncateString(&tmpString, B_TRUNCATE_MIDDLE, + Bounds().Width() - (fDivider + kBorderMargin)); + DrawString(tmpString.String()); + fSizeRect.right = fSizeRect.left + StringWidth(tmpString.String()) + 3; + } else { + DrawString(fSizeStr.String()); + fSizeRect.right = fSizeRect.left + StringWidth(fSizeStr.String()) + 3; + } + lineBase += lineHeight; + + // Created + SetHighColor(kAttrTitleColor); + MovePenTo(BPoint(fDivider - (StringWidth("Created:")), lineBase)); + SetHighColor(kAttrTitleColor); + DrawString("Created:"); + MovePenTo(BPoint(fDivider + kDrawMargin, lineBase)); + SetHighColor(kAttrValueColor); + DrawString(fCreatedStr.String()); + lineBase += lineHeight; + + // Modified + MovePenTo(BPoint(fDivider - (StringWidth("Modified:")), lineBase)); + SetHighColor(kAttrTitleColor); + DrawString("Modified:"); + MovePenTo(BPoint(fDivider + kDrawMargin, lineBase)); + SetHighColor(kAttrValueColor); + DrawString(fModifiedStr.String()); + lineBase += lineHeight; + + // Kind + MovePenTo(BPoint(fDivider - (StringWidth("Kind:")), lineBase)); + SetHighColor(kAttrTitleColor); + DrawString("Kind:"); + MovePenTo(BPoint(fDivider + kDrawMargin, lineBase)); + SetHighColor(kAttrValueColor); + DrawString(fKindStr.String()); + lineBase += lineHeight; + + BFont normalFont; + GetFont(&normalFont); + + // Path + MovePenTo(BPoint(fDivider - (StringWidth("Path:")), lineBase)); + SetHighColor(kAttrTitleColor); + DrawString("Path:"); + + MovePenTo(BPoint(fDivider + kDrawMargin, lineBase)); + SetHighColor(kLinkColor); + + // Check for truncation + if (StringWidth(fPathStr.String()) > (Bounds().Width() - (fDivider + kBorderMargin))) { + BString nameString(fPathStr.String()); + TruncateString(&nameString, B_TRUNCATE_MIDDLE, + Bounds().Width() - (fDivider + kBorderMargin)); + DrawString(nameString.String()); + } else + DrawString(fPathStr.String()); + + // Cache the position of the path + fPathRect.top = lineBase - fontMetrics.ascent; + fPathRect.bottom = lineBase + fontMetrics.descent; + fPathRect.left = fDivider + 2; + fPathRect.right = fPathRect.left + StringWidth(fPathStr.String()) + 3; + + lineBase += lineHeight; + + // Link to/version + if (fModel->IsSymLink()) { + MovePenTo(BPoint(fDivider - (StringWidth("Link To:")), lineBase)); + SetHighColor(kAttrTitleColor); + DrawString("Link To:"); + MovePenTo(BPoint(fDivider + kDrawMargin, lineBase)); + SetHighColor(kLinkColor); + + // Check for truncation + if (StringWidth(fLinkToStr.String()) > (Bounds().Width() - (fDivider + kBorderMargin))) { + BString nameString(fLinkToStr.String()); + TruncateString(&nameString, B_TRUNCATE_MIDDLE, + Bounds().Width() - (fDivider + kBorderMargin)); + DrawString(nameString.String()); + } else + DrawString(fLinkToStr.String()); + + // Cache the position of the link field + fLinkRect.top = lineBase - fontMetrics.ascent; + fLinkRect.bottom = lineBase + fontMetrics.descent; + fLinkRect.left = fDivider + 2; + fLinkRect.right = fLinkRect.left + StringWidth(fLinkToStr.String()) + 3; + + // No description field + fDescRect = BRect(-1, -1, -1, -1); + } else if (fModel->IsExecutable()) { + //Version + MovePenTo(BPoint(fDivider - (StringWidth("Version:")), lineBase)); + SetHighColor(kAttrTitleColor); + DrawString("Version:"); + MovePenTo(BPoint(fDivider + kDrawMargin, lineBase)); + SetHighColor(kAttrValueColor); + BString nameString; + if (fModel->GetVersionString(nameString, B_APP_VERSION_KIND) == B_OK) + DrawString(nameString.String()); + else + DrawString("-"); + lineBase += lineHeight; + + // Description + MovePenTo(BPoint(fDivider - (StringWidth("Description:")), lineBase)); + SetHighColor(kAttrTitleColor); + DrawString("Description:"); + MovePenTo(BPoint(fDivider + kDrawMargin, lineBase)); + SetHighColor(kAttrValueColor); + // Check for truncation + if (StringWidth(fDescStr.String()) > (Bounds().Width() - (fDivider + kBorderMargin))) { + BString nameString(fDescStr.String()); + TruncateString(&nameString, B_TRUNCATE_MIDDLE, + Bounds().Width() - (fDivider + kBorderMargin)); + DrawString(nameString.String()); + } else + DrawString(fDescStr.String()); + + // Cache the position of the description field + fDescRect.top = lineBase - fontMetrics.ascent; + fDescRect.bottom = lineBase + fontMetrics.descent; + fDescRect.left = fDivider + 2; + fDescRect.right = fDescRect.left + StringWidth(fDescStr.String()) + 3; + + // No link field + fLinkRect = BRect(-1, -1, -1, -1); + } +} + + +void +AttributeView::BeginEditingTitle() +{ + if (fTitleEditView != NULL) + return; + + BFont font; + GetFont(&font); + font.SetSize(kTitleFontHeight); + BRect textFrame(fTitleRect); + textFrame.right = Bounds().Width() - 5; + BRect textRect(textFrame); + textRect.OffsetTo(0, 0); + textRect.InsetBy(1, 1); + + // Just make it some really large size, since we don't do any line wrapping. + // The text filter will make sure to scroll the cursor into position + + textRect.right = 2000; + fTitleEditView = new BTextView(textFrame, "text_editor", + textRect, &font, 0, B_FOLLOW_ALL, B_WILL_DRAW); + fTitleEditView->SetText(fModel->Name()); + // Reset the width of the text rect + textRect = fTitleEditView->TextRect(); + textRect.right = fTitleEditView->LineWidth() + 20; + fTitleEditView->SetTextRect(textRect); + fTitleEditView->SetWordWrap(false); + // Add filter for catching B_RETURN and B_ESCAPE key's + fTitleEditView->AddFilter(new BMessageFilter(B_KEY_DOWN, AttributeView::TextViewFilter)); + + BScrollView *scrollView = new BScrollView("BorderView", + fTitleEditView, 0, 0, false, false, B_PLAIN_BORDER); + AddChild(scrollView); + fTitleEditView->SelectAll(); + fTitleEditView->MakeFocus(); + + Window()->UpdateIfNeeded(); +} + + +void +AttributeView::FinishEditingTitle(bool commit) +{ + if (fTitleEditView == NULL) + return; + + bool reopen = false; + + const char *text = fTitleEditView->Text(); + uint32 length = strlen(text); + if (commit && strcmp(text, fModel->Name()) != 0 && length < B_FILE_NAME_LENGTH) { + BEntry entry(fModel->EntryRef()); + BDirectory parent; + if (entry.InitCheck() == B_OK + && entry.GetParent(&parent) == B_OK) { + if (parent.Contains(text)) { + (new BAlert("", "That name is already taken. " + "Please type another one.", "OK", 0, 0, + B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); + reopen = true; + } else { + if (fModel->IsVolume()) { + BVolume volume(fModel->NodeRef()->device); + if (volume.InitCheck() == B_OK) + volume.SetName(text); + } else + entry.Rename(text); + + // Adjust the size of the text rect + BFont currentFont; + GetFont(¤tFont); + currentFont.SetSize(kTitleFontHeight); + fTitleRect.right = min_c(fTitleRect.left + + currentFont.StringWidth(fTitleEditView->Text()), Bounds().Width() - 5); + } + } + } else if (length >= B_FILE_NAME_LENGTH) { + (new BAlert("", "That name is too long. " + "Please type another one.", "OK", 0, 0, + B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); + reopen = true; + } + + // Remove view + BView *scrollView = fTitleEditView->Parent(); + RemoveChild(scrollView); + delete scrollView; + fTitleEditView = NULL; + + if (reopen) + BeginEditingTitle(); +} + + +void +AttributeView::MakeFocus(bool isFocus) +{ + if (!isFocus && fTitleEditView != NULL) + FinishEditingTitle(true); +} + + +void +AttributeView::WindowActivated(bool isFocus) +{ + if (!isFocus) { + if (fTitleEditView != NULL) + FinishEditingTitle(true); + + if (fPathWindow->Lock()) { + fPathWindow->Quit(); + fPathWindow = NULL; + } + + if (fLinkWindow->Lock()) { + fLinkWindow->Quit(); + fLinkWindow = NULL; + } + + if (fDescWindow->Lock()) { + fDescWindow->Quit(); + fDescWindow = NULL; + } + } +} + + +float +AttributeView::CurrentFontHeight(float size) +{ + BFont font; + GetFont(&font); + if (size > -1) + font.SetSize(size); + + font_height fontHeight; + font.GetHeight(&fontHeight); + + return fontHeight.ascent + fontHeight.descent + fontHeight.leading + 2; +} + + +status_t +AttributeView::BuildContextMenu(BMenu *parent) +{ + // Add navigation menu if this is not a symlink + // Symlink's to directories are OK however! + BEntry entry(fModel->EntryRef()); + entry_ref ref; + entry.GetRef(&ref); + Model model(&entry); + bool navigate = false; + if (model.InitCheck() == B_OK) { + if (model.IsSymLink()) { + // Check if it's to a directory + if (entry.SetTo(model.EntryRef(), true) == B_OK) { + navigate = entry.IsDirectory(); + entry.GetRef(&ref); + } + } else if (model.IsDirectory() || model.IsVolume()) + navigate = true; + } + ModelMenuItem *navigationItem = NULL; + if (navigate) { + navigationItem = new ModelMenuItem(new Model(model), + new BNavMenu(model.Name(), B_REFS_RECEIVED, be_app, Window())); + + // setup a navigation menu item which will dynamically load items + // as menu items are traversed + BNavMenu *navMenu = dynamic_cast(navigationItem->Submenu()); + navMenu->SetNavDir(&ref); + navigationItem->SetLabel(model.Name()); + navigationItem->SetEntry(&entry); + + parent->AddItem(navigationItem, 0); + parent->AddItem(new BSeparatorItem(), 1); + + BMessage *message = new BMessage(B_REFS_RECEIVED); + message->AddRef("refs", &ref); + navigationItem->SetMessage(message); + navigationItem->SetTarget(be_app); + } + + parent->AddItem(new BMenuItem("Open", new BMessage(kOpenSelection), 'O')); + + if (!FSIsTrashDir(&entry)) { + parent->AddItem(new BMenuItem("Edit Name", new BMessage(kEditItem), 'E')); + parent->AddSeparatorItem(); + if (fModel->IsVolume()) { + BMenuItem *item; + parent->AddItem(item = new BMenuItem("Unmount", new BMessage(kUnmountVolume), 'U')); + // volume model, enable/disable the Unmount item + BVolume boot; + BVolumeRoster().GetBootVolume(&boot); + BVolume volume; + volume.SetTo(fModel->NodeRef()->device); + if (volume == boot) + item->SetEnabled(false); + } else + parent->AddItem(new BMenuItem("Identify", new BMessage(kIdentifyEntry))); + } else if (FSIsTrashDir(&entry)) + parent->AddItem(new BMenuItem("Empty Trash", new BMessage(kEmptyTrash))); + + BMenuItem *sizeItem = NULL; + if (model.IsDirectory() && !model.IsVolume()) + parent->AddItem(sizeItem = new BMenuItem("Recalculate Folder Size", + new BMessage(kRecalculateSize))); + + if (model.IsSymLink()) + parent->AddItem(sizeItem = new BMenuItem("Set new link target", + new BMessage(kSetLinkTarget))); + + parent->AddItem(new BSeparatorItem()); + parent->AddItem(new BMenuItem("Permissions", new BMessage(kPermissionsSelected), 'P')); + + parent->SetFont(be_plain_font); + parent->SetTargetForItems(this); + + // Reset the nav menu to be_app + if (navigate) + navigationItem->SetTarget(be_app); + if (sizeItem) + sizeItem->SetTarget(Window()); + + return B_OK; +} + + +void +AttributeView::SetPermissionsSwitchState(int32 state) +{ + fPermissionsSwitch->SetValue(state); + fPermissionsSwitch->Invalidate(); +} + + +filter_result +AttributeView::TextViewFilter(BMessage *message, BHandler **, BMessageFilter *filter) +{ + uchar key; + AttributeView *attribView = static_cast( + static_cast(filter->Looper())->FindView("attr_view")); + + // Adjust the size of the text rect + BRect nuRect(attribView->TextView()->TextRect()); + nuRect.right = attribView->TextView()->LineWidth() + 20; + attribView->TextView()->SetTextRect(nuRect); + + // Make sure the cursor is in view + attribView->TextView()->ScrollToSelection(); + if (message->FindInt8("byte", (int8 *)&key) != B_OK) + return B_DISPATCH_MESSAGE; + + if (key == B_RETURN || key == B_ESCAPE) { + attribView->FinishEditingTitle(key == B_RETURN); + return B_SKIP_MESSAGE; + } + + return B_DISPATCH_MESSAGE; +} + + +off_t +AttributeView::LastSize() const +{ + return fLastSize; +} + + +void +AttributeView::SetLastSize(off_t lastSize) +{ + fLastSize = lastSize; +} + + +void +AttributeView::SetSizeStr(const char *sizeStr) +{ + fSizeStr = sizeStr; + + BRect bounds(Bounds()); + float lineHeight = CurrentFontHeight(kAttribFontHeight) + 6; + bounds.Set(fDivider, fIconRect.bottom, bounds.right, fIconRect.bottom + lineHeight); + Invalidate(bounds); +} + + +// #pragma mark - + + +TrackingView::TrackingView(BRect frame, const char *str, const BFont *font, BMessage *message) + : BControl(frame, "trackingView", str, message, B_FOLLOW_ALL, B_WILL_DRAW), + fMouseDown(false), + fMouseInView(false), + fFont(*font) +{ + SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + SetEventMask(B_POINTER_EVENTS, 0); +} + + +void +TrackingView::MouseDown(BPoint) +{ + if (Message() != NULL) { + fMouseDown = true; + fMouseInView = true; + InvertRect(Bounds()); + } +} + + +void +TrackingView::MouseMoved(BPoint, uint32 transit, const BMessage *) +{ + if ((transit == B_ENTERED_VIEW || transit == B_EXITED_VIEW) && fMouseDown) + InvertRect(Bounds()); + + fMouseInView = (transit == B_ENTERED_VIEW || transit == B_INSIDE_VIEW); + + if (!fMouseInView && !fMouseDown) + Window()->Close(); +} + + +void +TrackingView::MouseUp(BPoint) +{ + if (Message() != NULL) { + if (fMouseInView) Invoke(); + fMouseDown = false; + Window()->Close(); + } +} + + +void +TrackingView::Draw(BRect) +{ + if (Message() != NULL) + SetHighColor(kLinkColor); + else + SetHighColor(kAttrValueColor); + SetLowColor(ViewColor()); + + font_height fontHeight; + fFont.GetHeight(&fontHeight); + + DrawString(Label(), BPoint(3, Bounds().Height() - fontHeight.descent)); +} + diff --git a/src/kits/tracker/InfoWindow.h b/src/kits/tracker/InfoWindow.h new file mode 100644 index 0000000000..f7e95414d6 --- /dev/null +++ b/src/kits/tracker/InfoWindow.h @@ -0,0 +1,213 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef INFO_WINDOW_H +#define INFO_WINDOW_H + +#include +#include +#include + +#include "DialogPane.h" +#include "FilePermissionsView.h" +#include "LockingList.h" +#include "Utilities.h" + +class BMenuField; + +namespace BPrivate { + +class Model; +class AttributeView; +class TrackingView; + +// States for tracking the mouse +enum track_state { + no_track = 0, + link_track, + path_track, + icon_track, + size_track, + open_only_track // This is for items that can be opened, but can't be + // drag and dropped or renamed (Trash, Desktop Folder...) +}; + +class TrackingView : public BControl { + public: + TrackingView(BRect, const char *str, const BFont *font, BMessage *message); + + virtual void MouseDown(BPoint); + virtual void MouseMoved(BPoint, uint32 transit, const BMessage *message); + virtual void MouseUp(BPoint); + virtual void Draw(BRect); + + private: + bool fMouseDown; + bool fMouseInView; + BFont fFont; +}; + +class AttributeView : public BView { + public: + AttributeView(BRect, Model *); + ~AttributeView(); + + void ModelChanged(Model *, BMessage *); + void ReLinkTargetModel(Model *); + void BeginEditingTitle(); + void FinishEditingTitle(bool); + float CurrentFontHeight(float size = -1); + + BTextView *TextView() const { return fTitleEditView; } + + static filter_result TextViewFilter(BMessage *, BHandler **, BMessageFilter *); + + off_t LastSize() const; + void SetLastSize(off_t); + + void SetSizeStr(const char *); + + status_t BuildContextMenu(BMenu *parent); + + void SetPermissionsSwitchState(int32 state); + + protected: + virtual void MouseDown(BPoint); + virtual void MouseMoved(BPoint, uint32, const BMessage *); + virtual void MouseUp(BPoint); + virtual void MessageReceived(BMessage *); + virtual void AttachedToWindow(); + virtual void Draw(BRect); + virtual void Pulse(); + virtual void MakeFocus(bool); + virtual void WindowActivated(bool); + + private: + void InitStrings(const Model *); + void CheckAndSetSize(); + void OpenLinkSource(); + void OpenLinkTarget(); + + BString fPathStr; + BString fLinkToStr; + BString fSizeStr; + BString fModifiedStr; + BString fCreatedStr; + BString fKindStr; + BString fDescStr; + + off_t fFreeBytes; + off_t fLastSize; + + BRect fPathRect; + BRect fLinkRect; + BRect fDescRect; + BRect fTitleRect; + BRect fIconRect; + BRect fSizeRect; + BPoint fClickPoint; + float fDivider; + + BMenuField *fPreferredAppMenu; + Model *fModel; + Model *fIconModel; + BBitmap *fIcon; + bool fMouseDown; + bool fDragging; + bool fDoubleClick; + track_state fTrackingState; + bool fIsDropTarget; + BTextView *fTitleEditView; + PaneSwitch *fPermissionsSwitch; + BWindow *fPathWindow; + BWindow *fLinkWindow; + BWindow *fDescWindow; + + typedef BView _inherited; +}; + + +class BInfoWindow : public BWindow { + public: + BInfoWindow(Model *, int32 groupIndex, LockingList *list = NULL); + ~BInfoWindow(); + + virtual bool IsShowing(const node_ref *) const; + Model *TargetModel() const; + void SetSizeStr(const char *); + bool StopCalc(); + void OpenFilePanel(const entry_ref *); + + static void GetSizeString(BString &result, off_t size, int32 fileCount); + + protected: + virtual void Quit(); + virtual void MessageReceived(BMessage *); + virtual void Show(); + + private: + static BRect InfoWindowRect(bool displayingSymlink); + static int32 CalcSize(void *); + + Model *fModel; + volatile bool fStopCalc; + int32 fIndex; // tells where it lives with respect to other + thread_id fCalcThreadID; + LockingList *fWindowList; + FilePermissionsView *fPermissionsView; + AttributeView *fAttributeView; + BFilePanel *fFilePanel; + bool fFilePanelOpen; + + typedef BWindow _inherited; +}; + + +inline bool +BInfoWindow::StopCalc() +{ + return fStopCalc; +} + +inline Model * +BInfoWindow::TargetModel() const +{ + return fModel; +} + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/Jamfile b/src/kits/tracker/Jamfile new file mode 100644 index 0000000000..4048badb60 --- /dev/null +++ b/src/kits/tracker/Jamfile @@ -0,0 +1,88 @@ +SubDir OBOS_TOP src kits tracker ; + +UsePrivateHeaders shared ; +UsePrivateHeaders tracker ; + +AddResources libtracker.so : TrackerIcons.rdef ; + +SubDirC++Flags + -D_BUILDING_tracker=1 -DOPEN_TRACKER=1 +# -D_INCLUDES_CLASS_DEVICE_MAP=1 + -D_SUPPORTS_RESOURCES=1 + -D_SUPPORTS_FEATURE_SCRIPTING=1 +# -D_SILENTLY_CORRECT_FILE_NAMES=1 + ; + +SharedLibrary tracker : + AttributeStream.cpp + AutoMounter.cpp + AutoMounterSettings.cpp + BackgroundImage.cpp + Bitmaps.cpp + ContainerWindow.cpp + CountView.cpp + DeskWindow.cpp + DesktopPoseView.cpp + DialogPane.cpp + DirMenu.cpp + EntryIterator.cpp + FBCPadding.cpp + FSClipboard.cpp + FSUndoRedo.cpp + FSUtils.cpp + FavoritesConfig.cpp + FavoritesMenu.cpp + FilePanel.cpp + FilePanelPriv.cpp + FilePermissionsView.cpp + FindPanel.cpp + GroupedMenu.cpp + IconCache.cpp + IconMenuItem.cpp + InfoWindow.cpp + MimeTypeList.cpp + MiniMenuField.cpp + Model.cpp + MountMenu.cpp + Navigator.cpp + NavMenu.cpp + NodePreloader.cpp + NodeWalker.cpp + OpenWithWindow.cpp + OverrideAlert.cpp + PendingNodeMonitorCache.cpp + Pose.cpp + PoseList.cpp + PoseView.cpp + PoseViewScripting.cpp + QueryContainerWindow.cpp + QueryPoseView.cpp + RecentItems.cpp + RegExp.cpp + SelectionWindow.cpp + Settings.cpp + SettingsHandler.cpp + SettingsViews.cpp + SlowContextPopup.cpp + SlowMenu.cpp + StatusWindow.cpp + TaskLoop.cpp + TemplatesMenu.cpp + Tests.cpp + TextWidget.cpp + Thread.cpp + TitleView.cpp + Tracker.cpp + TrackerInitialState.cpp + TrackerScripting.cpp + TrackerSettings.cpp + TrackerSettingsWindow.cpp + TrackerString.cpp + TrashWatcher.cpp + Utilities.cpp + ViewState.cpp + VolumeWindow.cpp + WidgetAttributeText.cpp + ; + +LinkSharedOSLibs libtracker.so : libroot.so libbe.so libtranslation.so ; diff --git a/src/kits/tracker/LICENSE b/src/kits/tracker/LICENSE new file mode 100644 index 0000000000..cce2fba89f --- /dev/null +++ b/src/kits/tracker/LICENSE @@ -0,0 +1,32 @@ +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. + diff --git a/src/kits/tracker/LockingList.h b/src/kits/tracker/LockingList.h new file mode 100644 index 0000000000..05d5992d36 --- /dev/null +++ b/src/kits/tracker/LockingList.h @@ -0,0 +1,92 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef _LOCKING_LIST_H +#define _LOCKING_LIST_H + +#include +#include "ObjectList.h" + +namespace BPrivate { + +template +class LockingList : public BObjectList { +public: + LockingList(int32 itemsPerBlock = 20, bool owning = false); + ~LockingList() + { + Lock(); + } + + bool Lock(); + void Unlock(); + bool IsLocked() const; + +private: + BLocker lock; +}; + +template +LockingList::LockingList(int32 itemsPerBlock, bool owning) + : BObjectList(itemsPerBlock, owning) +{ +} + +template +bool +LockingList::Lock() +{ + return lock.Lock(); +} + +template +void +LockingList::Unlock() +{ + lock.Unlock(); +} + +template +bool +LockingList::IsLocked() const +{ + return lock.IsLocked(); +} + + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/MimeTypeList.cpp b/src/kits/tracker/MimeTypeList.cpp new file mode 100644 index 0000000000..a837c3ffaf --- /dev/null +++ b/src/kits/tracker/MimeTypeList.cpp @@ -0,0 +1,164 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include + +#include "AutoLock.h" +#include "MimeTypeList.h" +#include "Thread.h" + + +ShortMimeInfo::ShortMimeInfo(const BMimeType &mimeType) + : fCommonMimeType(true) +{ + fPrivateName = mimeType.Type(); + + char buffer[B_MIME_TYPE_LENGTH]; + + // weed out apps - their preferred handler is themselves + if (mimeType.GetPreferredApp(buffer) == B_OK + && fPrivateName.ICompare(buffer) == 0) + fCommonMimeType = false; + + // weed out metamimes without a short description + if (mimeType.GetShortDescription(buffer) != B_OK || buffer[0] == 0) + fCommonMimeType = false; + else + fShortDescription = buffer; +} + + +ShortMimeInfo::ShortMimeInfo(const char *shortDescription) + : fShortDescription(shortDescription) +{ +} + +const char * +ShortMimeInfo::InternalName() const +{ + return fPrivateName.String(); +} + +const char * +ShortMimeInfo::ShortDescription() const +{ + return fShortDescription.String(); +} + +int +ShortMimeInfo::CompareShortDescription(const ShortMimeInfo *a, const ShortMimeInfo *b) +{ + return a->fShortDescription.ICompare(b->fShortDescription); +} + +bool +ShortMimeInfo::IsCommonMimeType() const +{ + return fCommonMimeType; +} + + +// #pragma mark - + + +MimeTypeList::MimeTypeList() + : fMimeList(100, true), + fCommonMimeList(30, false), + fLock("mimeListLock") +{ + fLock.Lock(); + Thread::Launch(NewMemberFunctionObject(&MimeTypeList::Build, this), + B_NORMAL_PRIORITY); +} + +static int +MatchOneShortDescription(const ShortMimeInfo *a, const ShortMimeInfo *b) +{ + return strcasecmp(a->ShortDescription(), b->ShortDescription()); +} + +const ShortMimeInfo * +MimeTypeList::FindMimeType(const char *shortDescription) const +{ + ShortMimeInfo tmp(shortDescription); + const ShortMimeInfo *result = fCommonMimeList.BinarySearch(tmp, + &MatchOneShortDescription); + + return result; +} + +const ShortMimeInfo * +MimeTypeList::EachCommonType(bool (*func)(const ShortMimeInfo *, void *), + void *state) const +{ + AutoLock locker(fLock); + int32 count = fCommonMimeList.CountItems(); + for (int32 index = 0; index < count; index++) { + if ((func)(fCommonMimeList.ItemAt(index), state)) + return fCommonMimeList.ItemAt(index); + } + return NULL; +} + +void +MimeTypeList::Build() +{ + ASSERT(fLock.IsLocked()); + + BMessage message; + BMimeType::GetInstalledTypes(&message); + + int32 count; + uint32 type; + message.GetInfo("types", &type, &count); + + for (int32 index = 0; index < count; index++) { + const char *str; + if (message.FindString("types", index, &str) != B_OK) + continue; + + BMimeType mimetype(str); + if (mimetype.InitCheck() != B_OK) + continue; + + ShortMimeInfo *mimeInfo = new ShortMimeInfo(mimetype); + fMimeList.AddItem(mimeInfo); + if (mimeInfo->IsCommonMimeType()) + fCommonMimeList.AddItem(mimeInfo); + } + fCommonMimeList.SortItems(&ShortMimeInfo::CompareShortDescription); + fLock.Unlock(); +} + + diff --git a/src/kits/tracker/MimeTypeList.h b/src/kits/tracker/MimeTypeList.h new file mode 100644 index 0000000000..1b9ddb45b0 --- /dev/null +++ b/src/kits/tracker/MimeTypeList.h @@ -0,0 +1,91 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef __MIME_TYPE_LIST__ +#define __MIME_TYPE_LIST__ + +#include +#include "ObjectList.h" +#include "Utilities.h" + +namespace BPrivate { + +class MimeTypeList; + +class ShortMimeInfo { +public: + ShortMimeInfo(const BMimeType &); + + const char *InternalName() const; + const char *ShortDescription() const; + bool IsCommonMimeType() const; + static int CompareShortDescription(const ShortMimeInfo *, + const ShortMimeInfo *); + +private: + ShortMimeInfo(const char *shortDescription); + + BString fPrivateName; + BString fShortDescription; + bool fCommonMimeType; + + friend class MimeTypeList; +}; + +class MimeTypeList { +public: + MimeTypeList(); + + // attributes for type + // internal name from short description + // update notification + + const ShortMimeInfo *FindMimeType(const char *shortDescription) const; + const ShortMimeInfo *EachCommonType(bool (*)(const ShortMimeInfo *, void *), + void *) const; + +protected: + void Build(); + +private: + BObjectList fMimeList; + BObjectList fCommonMimeList; + mutable Benaphore fLock; +}; + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/MimeTypes.h b/src/kits/tracker/MimeTypes.h new file mode 100644 index 0000000000..47ab641312 --- /dev/null +++ b/src/kits/tracker/MimeTypes.h @@ -0,0 +1,63 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef _MIME_TYPES_H +#define _MIME_TYPES_H + +namespace BPrivate { + +#define B_FILE_MIMETYPE "application/octet-stream" +#define B_DIR_MIMETYPE "application/x-vnd.Be-directory" +#define B_VOLUME_MIMETYPE "application/x-vnd.Be-volume" +#define B_QUERY_MIMETYPE "application/x-vnd.Be-query" +#define B_QUERY_TEMPLATE_MIMETYPE "application/x-vnd.Be-queryTemplate" +#define B_LINK_MIMETYPE "application/x-vnd.Be-symlink" +#define B_ROOT_MIMETYPE "application/x-vnd.Be-root" +#define B_BOOKMARK_MIMETYPE "application/x-vnd.Be-bookmark" +#define B_PERSON_MIMETYPE "application/x-person" + +#define B_PRINTER_MIMETYPE "application/x-vnd.Be.printer" +#define B_PRINTER_SPOOL_MIMETYPE "application/x-vnd.Be.printer-spool" + +#define kPlainTextMimeType "text/plain" + +#define kBitmapMimeType "image/x-vnd.Be-bitmap" +#define kLargeIconType "icon/large" +#define kMiniIconType "icon/mini" + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/MiniMenuField.cpp b/src/kits/tracker/MiniMenuField.cpp new file mode 100644 index 0000000000..13d45807bd --- /dev/null +++ b/src/kits/tracker/MiniMenuField.cpp @@ -0,0 +1,162 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include +#include + +#include "MiniMenuField.h" +#include "Utilities.h" + +MiniMenuField::MiniMenuField(BRect frame, const char *name, BPopUpMenu *menu, + uint32 resizeFlags, uint32 flags) + : BView(frame, name, resizeFlags, flags), + fMenu(menu) +{ + SetFont(be_plain_font, B_FONT_FAMILY_AND_STYLE | B_FONT_SIZE); +} + +MiniMenuField::~MiniMenuField() +{ + delete fMenu; +} + +void +MiniMenuField::AttachedToWindow() +{ + if (Parent()) { + SetViewColor(Parent()->ViewColor()); + SetLowColor(Parent()->ViewColor()); + } + SetHighColor(0, 0, 0); +} + +void +MiniMenuField::MakeFocus(bool on) +{ + Invalidate(); + BView::MakeFocus(on); +} + +void +MiniMenuField::KeyDown(const char *bytes, int32 numBytes) +{ + switch (bytes[0]) { + case B_SPACE: + case B_DOWN_ARROW: + case B_RIGHT_ARROW: + // invoke from keyboard + fMenu->Go(ConvertToScreen(BPoint(4, 4)), true, true); + break; + + default: + BView::KeyDown(bytes, numBytes); + break; + } +} + +void +MiniMenuField::Draw(BRect) +{ + BRect bounds(Bounds()); + bounds.InsetBy(2, 2); + BRect rect(bounds); + rect.right--; + rect.bottom--; + + rgb_color darkest = tint_color(kBlack, 0.6f); + rgb_color dark = tint_color(kBlack, 0.4f); + rgb_color medium = dark; + rgb_color light = tint_color(kBlack, 0.03f); + + SetHighColor(medium); + + // draw frame and shadow + BeginLineArray(10); + AddLine(rect.RightTop(), rect.RightBottom(), darkest); + AddLine(rect.RightBottom(), rect.LeftBottom(), darkest); + AddLine(rect.LeftBottom(), rect.LeftTop(), medium); + AddLine(rect.LeftTop(), rect.RightTop(), medium); + AddLine(bounds.LeftBottom() + BPoint(2, 0), bounds.RightBottom(), dark); + AddLine(bounds.RightTop() + BPoint(0, 1), bounds.RightBottom(), dark); + rect.InsetBy(1, 1); + AddLine(rect.RightTop(), rect.RightBottom(), medium); + AddLine(rect.RightBottom(), rect.LeftBottom(), medium); + AddLine(rect.LeftBottom(), rect.LeftTop(), light); + AddLine(rect.LeftTop(), rect.RightTop(), light); + + EndLineArray(); + + // draw triangle + rect = BRect(5, 5, 15, 15); + const rgb_color outlineColor = kBlack; + const rgb_color middleColor = {150, 150, 150, 255}; + + BeginLineArray(5); + AddLine(BPoint(rect.left + 3, rect.top + 1), + BPoint(rect.left + 3, rect.top + 7), outlineColor); + AddLine(BPoint(rect.left + 3, rect.top + 1), + BPoint(rect.left + 6, rect.top + 4), outlineColor); + AddLine(BPoint(rect.left + 6, rect.top + 4), + BPoint(rect.left + 3, rect.top + 7), outlineColor); + + AddLine(BPoint(rect.left + 4, rect.top + 3), + BPoint(rect.left + 4, rect.top + 5), middleColor); + AddLine(BPoint(rect.left + 5, rect.top + 4), + BPoint(rect.left + 5, rect.top + 4), middleColor); + EndLineArray(); + + // draw focus if focused, else erase focus + bounds = Bounds(); + bool focused = IsFocus() && Window()->IsActive(); + rgb_color markColor = ui_color(B_KEYBOARD_NAVIGATION_COLOR); + rgb_color viewColor = ViewColor(); + BeginLineArray(4); + AddLine(BPoint(bounds.left, bounds.top), + BPoint(bounds.right, bounds.top), focused ? markColor : viewColor); + AddLine(BPoint(bounds.right, bounds.top), + BPoint(bounds.right, bounds.bottom), focused ? markColor : viewColor); + AddLine(BPoint(bounds.right, bounds.bottom), + BPoint(bounds.left, bounds.bottom), focused ? markColor : viewColor); + AddLine(BPoint(bounds.left, bounds.bottom), + BPoint(bounds.left, bounds.top), focused ? markColor : viewColor); + EndLineArray(); + +} + +void +MiniMenuField::MouseDown(BPoint) +{ + fMenu->Go(ConvertToScreen(BPoint(4, 4)), true); +} + diff --git a/src/kits/tracker/MiniMenuField.h b/src/kits/tracker/MiniMenuField.h new file mode 100644 index 0000000000..b034b9a48a --- /dev/null +++ b/src/kits/tracker/MiniMenuField.h @@ -0,0 +1,70 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef __MINI_MENU_FIELD__ +#define __MINI_MENU_FIELD__ + +#include + +class BPopUpMenu; + +namespace BPrivate { + + +class MiniMenuField : public BView { +public: + MiniMenuField(BRect frame, const char *name, BPopUpMenu *menu, + uint32 resizeFlags = B_FOLLOW_LEFT | B_FOLLOW_TOP, + uint32 flags = B_WILL_DRAW | B_NAVIGABLE); + // ToDo: + // use BMenu instead of BPopUpMenu here + + virtual ~MiniMenuField(); + +protected: + virtual void AttachedToWindow(); + virtual void Draw(BRect); + virtual void MouseDown(BPoint ); + virtual void MakeFocus(bool); + virtual void KeyDown(const char *, int32); + +private: + BPopUpMenu *fMenu; +}; + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/Model.cpp b/src/kits/tracker/Model.cpp new file mode 100644 index 0000000000..cf0ee972b6 --- /dev/null +++ b/src/kits/tracker/Model.cpp @@ -0,0 +1,1450 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// Dedicated to BModel + +// ToDo: +// Consider moving iconFrom logic to BPose +// use a more efficient way of storing file type and preferred app strings + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Model.h" + +#include "Attributes.h" +#include "Bitmaps.h" +#include "FindPanel.h" +#include "FSUtils.h" +#include "MimeTypes.h" +#include "IconCache.h" +#include "Tracker.h" +#include "Utilities.h" + +#ifdef CHECK_OPEN_MODEL_LEAKS +BObjectList *writableOpenModelList = NULL; +BObjectList *readOnlyOpenModelList = NULL; +#endif + +namespace BPrivate { +extern +#if !B_BEOS_VERSION_DANO +_IMPEXP_BE +#endif +bool CheckNodeIconHintPrivate(const BNode *, bool); +} + + +Model::Model() + : + fPreferredAppName(NULL), + fBaseType(kUnknownNode), + fIconFrom(kUnknownSource), + fWritable(false), + fNode(NULL), + fStatus(B_NO_INIT) +{ +} + + +Model::Model(const Model &cloneThis) + : + fEntryRef(cloneThis.fEntryRef), + fMimeType(cloneThis.fMimeType), + fPreferredAppName(NULL), + fBaseType(cloneThis.fBaseType), + fIconFrom(cloneThis.fIconFrom), + fWritable(false), + fNode(NULL) +{ + fStatBuf.st_dev = cloneThis.NodeRef()->device; + fStatBuf.st_ino = cloneThis.NodeRef()->node; + + if (cloneThis.IsSymLink() && cloneThis.LinkTo()) + fLinkTo = new Model(*cloneThis.LinkTo()); + + fStatus = OpenNode(cloneThis.IsNodeOpenForWriting()); + if (fStatus == B_OK) { + ASSERT(fNode); + fNode->GetStat(&fStatBuf); + ASSERT(fStatBuf.st_dev == cloneThis.NodeRef()->device); + ASSERT(fStatBuf.st_ino == cloneThis.NodeRef()->node); + } + if (!cloneThis.IsNodeOpen()) + CloseNode(); +} + + +Model::Model(const node_ref *dirNode, const node_ref *node, const char *name, + bool open, bool writable) + : + fPreferredAppName(NULL), + fWritable(false), + fNode(NULL) +{ + SetTo(dirNode, node, name, open, writable); +} + + +Model::Model(const BEntry *entry, bool open, bool writable) + : + fPreferredAppName(NULL), + fWritable(false), + fNode(NULL) +{ + SetTo(entry, open, writable); +} + + +Model::Model(const entry_ref *ref, bool traverse, bool open, bool writable) + : + fPreferredAppName(NULL), + fBaseType(kUnknownNode), + fIconFrom(kUnknownSource), + fWritable(false), + fNode(NULL) +{ + BEntry entry(ref, traverse); + fStatus = entry.InitCheck(); + if (fStatus == B_OK) + SetTo(&entry, open, writable); +} + + +void +Model::DeletePreferredAppVolumeNameLinkTo() +{ + if (IsSymLink()) { + Model *tmp = fLinkTo; + // deal with link to link to self + fLinkTo = NULL; + delete tmp; + + } else if (IsVolume()) + free(fVolumeName); + else + free(fPreferredAppName); + + fPreferredAppName = NULL; +} + + +Model::~Model() +{ +#ifdef CHECK_OPEN_MODEL_LEAKS + if (writableOpenModelList) + writableOpenModelList->RemoveItem(this); + if (readOnlyOpenModelList) + readOnlyOpenModelList->RemoveItem(this); +#endif + + DeletePreferredAppVolumeNameLinkTo(); + if (IconCache::NeedsDeletionNotification((IconSource)fIconFrom)) + // this check allows us to use temporary Model in the IconCache + // without the danger of a deadlock + IconCache::sIconCache->Deleting(this); +#if xDEBUG + if (fNode) + PRINT(("destructor closing node for %s\n", Name())); +#endif + + delete fNode; +} + + +status_t +Model::SetTo(const BEntry *entry, bool open, bool writable) +{ + delete fNode; + fNode = NULL; + DeletePreferredAppVolumeNameLinkTo(); + fIconFrom = kUnknownSource; + fBaseType = kUnknownNode; + fMimeType = ""; + + fStatus = entry->GetRef(&fEntryRef); + if (fStatus != B_OK) + return fStatus; + + fStatus = entry->GetStat(&fStatBuf); + if (fStatus != B_OK) + return fStatus; + + fStatus = OpenNode(writable); + if (!open) + CloseNode(); + + return fStatus; +} + + +status_t +Model::SetTo(const entry_ref *newRef, bool traverse, bool open, bool writable) +{ + delete fNode; + fNode = NULL; + DeletePreferredAppVolumeNameLinkTo(); + fIconFrom = kUnknownSource; + fBaseType = kUnknownNode; + fMimeType = ""; + + BEntry tmpEntry(newRef, traverse); + fStatus = tmpEntry.InitCheck(); + if (fStatus != B_OK) + return fStatus; + + if (traverse) + tmpEntry.GetRef(&fEntryRef); + else + fEntryRef = *newRef; + + fStatus = tmpEntry.GetStat(&fStatBuf); + if (fStatus != B_OK) + return fStatus; + + fStatus = OpenNode(writable); + if (!open) + CloseNode(); + + return fStatus; +} + + +status_t +Model::SetTo(const node_ref *dirNode, const node_ref *nodeRef, const char *name, + bool open, bool writable) +{ + delete fNode; + fNode = NULL; + DeletePreferredAppVolumeNameLinkTo(); + fIconFrom = kUnknownSource; + fBaseType = kUnknownNode; + fMimeType = ""; + + fStatBuf.st_dev = nodeRef->device; + fStatBuf.st_ino = nodeRef->node; + fEntryRef.device = dirNode->device; + fEntryRef.directory = dirNode->node; + fEntryRef.name = strdup(name); + + BEntry tmpNode(&fEntryRef); + fStatus = tmpNode.InitCheck(); + if (fStatus != B_OK) + return fStatus; + + fStatus = tmpNode.GetStat(&fStatBuf); + if (fStatus != B_OK) + return fStatus; + + fStatus = OpenNode(writable); + + if (!open) + CloseNode(); + + return fStatus; +} + + +status_t +Model::InitCheck() const +{ + return fStatus; +} + + +int +Model::CompareFolderNamesFirst(const Model *compareModel) const +{ + if (compareModel == NULL) + return -1; + + const Model *resolvedCompareModel = compareModel->ResolveIfLink(); + const Model *resolvedMe = ResolveIfLink(); + + if (resolvedMe->IsVolume()) { + if (!resolvedCompareModel->IsVolume()) + return -1; + } else if (resolvedCompareModel->IsVolume()) + return 1; + + if (resolvedMe->IsDirectory()) { + if (!resolvedCompareModel->IsDirectory()) + return -1; + } else if (resolvedCompareModel->IsDirectory()) + return 1; + + return strcasecmp(Name(), compareModel->Name()); +} + + +const char * +Model::Name() const +{ + switch (fBaseType) { + case kRootNode: + return "Disks"; + case kVolumeNode: + if (fVolumeName) + return fVolumeName; + // fall thru + } + return fEntryRef.name; +} + + +status_t +Model::OpenNode(bool writable) +{ + if (IsNodeOpen() && (writable == IsNodeOpenForWriting())) + return B_OK; + + OpenNodeCommon(writable); + return fStatus; +} + + +status_t +Model::UpdateStatAndOpenNode(bool writable) +{ + if (IsNodeOpen() && (writable == IsNodeOpenForWriting())) + return B_OK; + + // try reading the stat structure again + BEntry tmpEntry(&fEntryRef); + fStatus = tmpEntry.InitCheck(); + if (fStatus != B_OK) + return fStatus; + + fStatus = tmpEntry.GetStat(&fStatBuf); + if (fStatus != B_OK) + return fStatus; + + OpenNodeCommon(writable); + return fStatus; +} + + +status_t +Model::OpenNodeCommon(bool writable) +{ +#if xDEBUG + PRINT(("opening node for %s\n", Name())); +#endif + +#ifdef CHECK_OPEN_MODEL_LEAKS + if (writableOpenModelList) + writableOpenModelList->RemoveItem(this); + if (readOnlyOpenModelList) + readOnlyOpenModelList->RemoveItem(this); +#endif + + if (fBaseType == kUnknownNode) + SetupBaseType(); + + switch (fBaseType) { + case kPlainNode: + case kExecutableNode: + case kQueryNode: + case kQueryTemplateNode: + // open or reopen + delete fNode; + fNode = new BFile(&fEntryRef, (uint32)(writable ? O_RDWR : O_RDONLY)); + break; + + case kDirectoryNode: + case kVolumeNode: + case kRootNode: + if (!IsNodeOpen()) + fNode = new BDirectory(&fEntryRef); + + if (fBaseType == kDirectoryNode + && static_cast(fNode)->IsRootDirectory()) { + // promote from directory to volume + fBaseType = kVolumeNode; + } + break; + + case kLinkNode: + if (!IsNodeOpen()) { + BEntry entry(&fEntryRef); + fNode = new BSymLink(&entry); + } + break; + + default: +#if DEBUG + PrintToStream(); +#endif + TRESPASS(); + // this can only happen if GetStat failed before, in which case + // we shouldn't be here + // ToDo: Obviously, we can also be here if the type could not be determined, + // for example for block devices (so the TRESPASS() macro shouldn't be + // used here)! + return fStatus = B_ERROR; + } + + fStatus = fNode->InitCheck(); + if (fStatus != B_OK) { + delete fNode; + fNode = NULL; + // original code snoozed an error here and returned B_OK + return fStatus; + } + + fWritable = writable; + + if (!fMimeType.Length()) + FinishSettingUpType(); + +#ifdef CHECK_OPEN_MODEL_LEAKS + if (fWritable) { + if (!writableOpenModelList) { + TRACE(); + writableOpenModelList = new BObjectList(100); + } + writableOpenModelList->AddItem(this); + } else { + if (!readOnlyOpenModelList) { + TRACE(); + readOnlyOpenModelList = new BObjectList(100); + } + readOnlyOpenModelList->AddItem(this); + } +#endif + + return fStatus; +} + + +void +Model::CloseNode() +{ +#if xDEBUG + PRINT(("closing node for %s\n", Name())); +#endif + +#ifdef CHECK_OPEN_MODEL_LEAKS + if (writableOpenModelList) + writableOpenModelList->RemoveItem(this); + if (readOnlyOpenModelList) + readOnlyOpenModelList->RemoveItem(this); +#endif + + delete fNode; + fNode = NULL; +} + + +bool +Model::IsNodeOpen() const +{ + return fNode != NULL; +} + + + +bool +Model::IsNodeOpenForWriting() const +{ + return fNode != NULL && fWritable; +} + + +void +Model::SetupBaseType() +{ + switch (fStatBuf.st_mode & S_IFMT) { + case S_IFDIR: + // folder + fBaseType = kDirectoryNode; + break; + + case S_IFREG: + // regular file + if (fStatBuf.st_mode & S_IXUSR) + // executable + fBaseType = kExecutableNode; + else + // non-executable + fBaseType = kPlainNode; + break; + + case S_IFLNK: + // symlink + fBaseType = kLinkNode; + break; + + default: + fBaseType = kUnknownNode; + break; + } +} + + +void +Model::FinishSettingUpType() +{ + char mimeString[B_MIME_TYPE_LENGTH]; + + // while we are reading the node, do a little + // snooping to see if it even makes sense to look for a node-based + // icon + // This serves as a hint to the icon cache, allowing it to not hit the + // disk again for models that do not have an icon defined by the node + if (IsNodeOpen() + && fBaseType != kLinkNode + && !CheckNodeIconHintPrivate(fNode, dynamic_cast(be_app) == NULL)) { + // when checking for the node icon hint, if we are libtracker, only check + // for small icons - checking for the large icons is a little more + // work for the filesystem and this will speed up the test. + // This makes node icons only work if there is a small and a large node + // icon on a file - for libtracker that is not a problem though + fIconFrom = kUnknownNotFromNode; + } + + if (fBaseType != kDirectoryNode + && fBaseType != kVolumeNode + && fBaseType != kLinkNode + && IsNodeOpen()) { + BNodeInfo info(fNode); + + // check if a specific mime type is set + if (info.GetType(mimeString) == B_OK) { + // node has a specific mime type + fMimeType = mimeString; + if (strcmp(mimeString, B_QUERY_MIMETYPE) == 0) + fBaseType = kQueryNode; + else if (strcmp(mimeString, B_QUERY_TEMPLATE_MIMETYPE) == 0) + fBaseType = kQueryTemplateNode; + + if (info.GetPreferredApp(mimeString) == B_OK) { + if (fPreferredAppName) + DeletePreferredAppVolumeNameLinkTo(); + + if (mimeString[0]) + fPreferredAppName = strdup(mimeString); + } + } + } + + switch (fBaseType) { + case kDirectoryNode: + fMimeType = B_DIR_MIMETYPE; // should use a shared string here + if (IsNodeOpen()) { + BNodeInfo info(fNode); + if (info.GetType(mimeString) == B_OK) + fMimeType = mimeString; + + if (fIconFrom == kUnknownNotFromNode + && WellKnowEntryList::Match(NodeRef()) > (directory_which)-1) + // one of home, beos, system, boot, etc. + fIconFrom = kTrackerSupplied; + } + break; + + case kVolumeNode: + { + if (NodeRef()->node == fEntryRef.directory + && NodeRef()->device == fEntryRef.device) { + // promote from volume to file system root + fBaseType = kRootNode; + fMimeType = B_ROOT_MIMETYPE; + break; + } + + // volumes have to have a B_VOLUME_MIMETYPE type + fMimeType = B_VOLUME_MIMETYPE; + if (fIconFrom == kUnknownNotFromNode) { + if (WellKnowEntryList::Match(NodeRef()) > (directory_which)-1) + fIconFrom = kTrackerSupplied; + else + fIconFrom = kVolume; + } + + char name[B_FILE_NAME_LENGTH]; + BVolume volume(NodeRef()->device); + if (volume.InitCheck() == B_OK && volume.GetName(name) == B_OK) { + if (fVolumeName) + DeletePreferredAppVolumeNameLinkTo(); + + fVolumeName = strdup(name); + } +#if DEBUG + else + PRINT(("get volume name failed for %s\n", fEntryRef.name)); +#endif + break; + } + + case kLinkNode: + fMimeType = B_LINK_MIMETYPE; // should use a shared string here + break; + + case kExecutableNode: + if (IsNodeOpen()) { + char signature[B_MIME_TYPE_LENGTH]; + if (GetAppSignatureFromAttr(dynamic_cast(fNode), signature) + == B_OK) { + + if (fPreferredAppName) + DeletePreferredAppVolumeNameLinkTo(); + + if (signature[0]) + fPreferredAppName = strdup(signature); + } + } + if (!fMimeType.Length()) + fMimeType = B_APP_MIME_TYPE; // should use a shared string here + break; + + default: + if (!fMimeType.Length()) + fMimeType = B_FILE_MIMETYPE; + break; + } +} + + +void +Model::ResetIconFrom() +{ + BModelOpener opener(this); + + if (InitCheck() != B_OK) + return; + + // mirror the logic from FinishSettingUpType + if ((fBaseType == kDirectoryNode || fBaseType == kVolumeNode) + && !CheckNodeIconHintPrivate(fNode, dynamic_cast(be_app) == NULL)) { + if (WellKnowEntryList::Match(NodeRef()) > (directory_which)-1) { + fIconFrom = kTrackerSupplied; + return; + } else if (dynamic_cast(fNode)->IsRootDirectory()) { + fIconFrom = kVolume; + return; + } + } + fIconFrom = kUnknownSource; +} + + +const char * +Model::PreferredAppSignature() const +{ + if (IsVolume() || IsSymLink()) + return ""; + + return fPreferredAppName ? fPreferredAppName : ""; +} + + +void +Model::SetPreferredAppSignature(const char *signature) +{ + ASSERT(!IsVolume() && !IsSymLink()); + ASSERT(signature != fPreferredAppName); + // self assignment should not be an option + + free(fPreferredAppName); + if (signature) + fPreferredAppName = strdup(signature); + else + fPreferredAppName = NULL; +} + + +const Model * +Model::ResolveIfLink() const +{ + if (!IsSymLink()) + return this; + + if (!fLinkTo) + return this; + + return fLinkTo; +} + + +Model * +Model::ResolveIfLink() +{ + if (!IsSymLink()) + return this; + + if (!fLinkTo) + return this; + + return fLinkTo; +} + + +void +Model::SetLinkTo(Model *model) +{ + ASSERT(IsSymLink()); + ASSERT(!fLinkTo || (fLinkTo != model)); + + if (fLinkTo) + delete fLinkTo; + fLinkTo = model; +} + + +void +Model::GetPreferredAppForBrokenSymLink(BString &result) +{ + if (!IsSymLink() || LinkTo()) { + result = ""; + return; + } + + BModelOpener opener(this); + BNodeInfo info(fNode); + status_t error = info.GetPreferredApp(result.LockBuffer(B_MIME_TYPE_LENGTH)); + result.UnlockBuffer(); + + if (error != B_OK) + // Tracker will have to do + result = kTrackerSignature; +} + + +// Node monitor updating stuff + +void +Model::UpdateEntryRef(const node_ref *dirNode, const char *name) +{ + if (IsVolume()) { + if (fVolumeName) + DeletePreferredAppVolumeNameLinkTo(); + + fVolumeName = strdup(name); + } + + fEntryRef.device = dirNode->device; + fEntryRef.directory = dirNode->node; + + if (fEntryRef.name && strcmp(fEntryRef.name, name) == 0) + return; + + fEntryRef.set_name(name); +} + + +status_t +Model::WatchVolumeAndMountPoint(uint32 , BHandler *target) +{ + ASSERT(IsVolume()); + + if (fEntryRef.name && fVolumeName + && strcmp(fEntryRef.name, "boot") == 0) { + // watch mount point for boot volume + BString bootMountPoint("/"); + bootMountPoint += fVolumeName; + BEntry mountPointEntry(bootMountPoint.String()); + Model mountPointModel(&mountPointEntry); + + TTracker::WatchNode(mountPointModel.NodeRef(), B_WATCH_NAME + | B_WATCH_STAT | B_WATCH_ATTR, target); + } + + return TTracker::WatchNode(NodeRef(), B_WATCH_NAME | B_WATCH_STAT + | B_WATCH_ATTR, target); +} + + +bool +Model::AttrChanged(const char *attrName) +{ + // called on an attribute changed node monitor + // sync up cached values of mime type and preferred app and + // return true if icon needs updating + + ASSERT(IsNodeOpen()); + if (attrName + && (strcmp(attrName, kAttrMiniIcon) == 0 + || strcmp(attrName, kAttrLargeIcon) == 0)) + return true; + + if (!attrName + || strcmp(attrName, kAttrMIMEType) == 0 + || strcmp(attrName, kAttrPreferredApp) == 0) { + char mimeString[B_MIME_TYPE_LENGTH]; + BNodeInfo info(fNode); + if (info.GetType(mimeString) != B_OK) + fMimeType = ""; + else { + // node has a specific mime type + fMimeType = mimeString; + if (!IsVolume() + && !IsSymLink() + && info.GetPreferredApp(mimeString) == B_OK) + SetPreferredAppSignature(mimeString); + } + +#if xDEBUG + if (fIconFrom != kNode) + PRINT(("%s, %s:updating icon because file type changed\n", + Name(), attrName ? attrName : "")); + else + PRINT(("not updating icon even thoug type changed " + "because icon from node\n")); + +#endif + + return fIconFrom != kNode; + // update icon unless it is comming from a node + } + + return attrName == NULL; +} + + +bool +Model::StatChanged() +{ + ASSERT(IsNodeOpen()); + mode_t oldMode = fStatBuf.st_mode; + fStatus = fNode->GetStat(&fStatBuf); + if (oldMode != fStatBuf.st_mode) { + bool forWriting = IsNodeOpenForWriting(); + CloseNode(); + //SetupBaseType(); + // the node type can't change with a stat update... + OpenNodeCommon(forWriting); + return true; + } + return false; +} + +// Mime handling stuff + +bool +Model::IsDropTarget(const Model *forDocument, bool traverse) const +{ + switch (CanHandleDrops()) { + case kCanHandle: + return true; + + case kCannotHandle: + return false; + + default: + break; + } + if (!forDocument) + return true; + + if (traverse) { + BEntry entry(forDocument->EntryRef(), true); + if (entry.InitCheck() != B_OK) + return false; + + BFile file(&entry, O_RDONLY); + BNodeInfo mime(&file); + + if (mime.InitCheck() != B_OK) + return false; + + char mimeType[B_MIME_TYPE_LENGTH]; + mime.GetType(mimeType); + + return SupportsMimeType(mimeType, 0) != kDoesNotSupportType; + } + // do some mime-based matching + const char *documentMimeType = forDocument->MimeType(); + if (!documentMimeType) + return false; + + return SupportsMimeType(documentMimeType, 0) != kDoesNotSupportType; +} + + +Model::CanHandleResult +Model::CanHandleDrops() const +{ + if (IsDirectory()) + // directories take anything + // resolve permissions here + return kCanHandle; + + + if (IsSymLink()) { + // descend into symlink and try again on it's target + + BEntry entry(&fEntryRef, true); + if (entry.InitCheck() != B_OK) + return kCannotHandle; + + if (entry == BEntry(EntryRef())) + // self-referencing link, avoid infinite recursion + return kCannotHandle; + + Model model(&entry); + if (model.InitCheck() != B_OK) + return kCannotHandle; + + return model.CanHandleDrops(); + } + + if (IsExecutable()) + return kNeedToCheckType; + + return kCannotHandle; +} + + +inline bool +IsSuperHandlerSignature(const char *signature) +{ + return strcasecmp(signature, B_FILE_MIMETYPE) == 0; +} + + +enum { + kDontMatch = 0, + kMatchSupertype, + kMatch +}; + +static int32 +MatchMimeTypeString(/*const */BString *documentType, const char *handlerType) +{ + // perform a mime type wildcard match + // handler types of the form "text" + // handle every handled type with same supertype, + // for everything else a full string match is used + + int32 supertypeOnlyLength = 0; + const char *tmp = strstr(handlerType, "/"); + + if (!tmp) + // no subtype - supertype string only + supertypeOnlyLength = (int32)strlen(handlerType); + + if (supertypeOnlyLength) { + // compare just the supertype + tmp = strstr(documentType->String(), "/"); + if (tmp && (tmp - documentType->String() == supertypeOnlyLength)) { + if (documentType->ICompare(handlerType, supertypeOnlyLength) == 0) + return kMatchSupertype; + else + return kDontMatch; + } + } + + if (documentType->ICompare(handlerType) == 0) + return kMatch; + + return kDontMatch; +} + + +int32 +Model::SupportsMimeType(const char *type, const BObjectList *list, + bool exactReason) const +{ + ASSERT((type == 0) != (list == 0)); + // pass in one or the other + + int32 result = kDoesNotSupportType; + + BFile file(EntryRef(), O_RDONLY); + BAppFileInfo handlerInfo(&file); + + BMessage message; + if (handlerInfo.GetSupportedTypes(&message) != B_OK) + return kDoesNotSupportType; + + for (int32 index = 0; ; index++) { + + // check if this model lists the type of dropped document as supported + const char *mimeSignature; + int32 bufferLength; + + if (message.FindData("types", 'CSTR', index, (const void **)&mimeSignature, + &bufferLength)) + return result; + + if (IsSuperHandlerSignature(mimeSignature)) { + if (!exactReason) + return kSuperhandlerModel; + + if (result == kDoesNotSupportType) + result = kSuperhandlerModel; + } + + int32 match; + + if (type) { + BString typeString(type); + match = MatchMimeTypeString(&typeString, mimeSignature); + } else + match = WhileEachListItem(const_cast *>(list), + MatchMimeTypeString, mimeSignature); + // const_cast shouldnt be here, have to have it until MW cleans up + + if (match == kMatch) + // supports the actual type, it can't get any better + return kModelSupportsType; + else if (match == kMatchSupertype) { + if (!exactReason) + return kModelSupportsSupertype; + + // we already know this model supports the file as a supertype, + // now find out if it matches the type + result = kModelSupportsSupertype; + } + } + + return result; +} + + +bool +Model::IsDropTargetForList(const BObjectList *list) const +{ + switch (CanHandleDrops()) { + case kCanHandle: + return true; + + case kCannotHandle: + return false; + + default: + break; + } + return SupportsMimeType(0, list) != kDoesNotSupportType; +} + + +bool +Model::IsSuperHandler() const +{ + ASSERT(CanHandleDrops() == kNeedToCheckType); + + BFile file(EntryRef(), O_RDONLY); + BAppFileInfo handlerInfo(&file); + + BMessage message; + if (handlerInfo.GetSupportedTypes(&message) != B_OK) + return false; + + for (int32 index = 0; ; index++) { + const char *mimeSignature; + int32 bufferLength; + + if (message.FindData("types", 'CSTR', index, (const void **)&mimeSignature, + &bufferLength)) + return false; + + if (IsSuperHandlerSignature(mimeSignature)) + return true; + } + return false; +} + + +void +Model::GetEntry(BEntry *entry) const +{ + entry->SetTo(EntryRef()); +} + + +void +Model::GetPath(BPath *path) const +{ + BEntry entry(EntryRef()); + entry.GetPath(path); +} + + +bool +Model::Mimeset(bool force) +{ + BString oldType = MimeType(); + ModelNodeLazyOpener opener(this); + BPath path; + GetPath(&path); + if (force) { + if (opener.OpenNode(true) != B_OK) + return false; + + Node()->RemoveAttr(kAttrMIMEType); + update_mime_info(path.Path(), 0, 1, 1); + } else + update_mime_info(path.Path(), 0, 1, 0); + + AttrChanged(0); + + return !oldType.ICompare(MimeType()); +} + + +ssize_t +Model::WriteAttr(const char *attr, type_code type, off_t offset, + const void *buffer, size_t length) +{ + BModelWriteOpener opener(this); + if (!fNode) + return 0; + + ssize_t result = fNode->WriteAttr(attr, type, offset, buffer, length); + return result; +} + + +ssize_t +Model::WriteAttrKillForegin(const char *attr, const char *foreignAttr, + type_code type, off_t offset, const void *buffer, size_t length) +{ + BModelWriteOpener opener(this); + if (!fNode) + return 0; + + ssize_t result = fNode->WriteAttr(attr, type, offset, buffer, length); + if (result == (ssize_t)length) + // nuke attribute in opposite endianness + fNode->RemoveAttr(foreignAttr); + return result; +} + + +status_t +Model::GetLongVersionString(BString &result, version_kind kind) +{ + BFile file(EntryRef(), O_RDONLY); + status_t error = file.InitCheck(); + if (error != B_OK) + return error; + + BAppFileInfo info(&file); + error = info.InitCheck(); + if (error != B_OK) + return error; + + version_info version; + error = info.GetVersionInfo(&version, kind); + if (error != B_OK) + return error; + + result = version.long_info; + return B_OK; +} + +status_t +Model::GetVersionString(BString &result, version_kind kind) +{ + BFile file(EntryRef(), O_RDONLY); + status_t error = file.InitCheck(); + if (error != B_OK) + return error; + + BAppFileInfo info(&file); + error = info.InitCheck(); + if (error != B_OK) + return error; + + version_info version; + error = info.GetVersionInfo(&version, kind); + if (error != B_OK) + return error; + + char vstr[32]; + sprintf(vstr, "%ld.%ld.%ld", version.major, version.middle, version.minor); + result = vstr; + return B_OK; +} + +#if DEBUG + +void +Model::PrintToStream(int32 level, bool deep) +{ + PRINT(("model name %s, entry name %s, inode %Lx, dev %x, directory inode %Lx\n", + Name() ? Name() : "**empty name**", + EntryRef()->name ? EntryRef()->name : "**empty ref name**", + NodeRef()->node, + NodeRef()->device, + EntryRef()->directory)); + PRINT(("type %s \n", MimeType())); + + PRINT(("model type: ")); + switch (fBaseType) { + case kPlainNode: + PRINT(("plain\n")); + break; + + case kQueryNode: + PRINT(("query\n")); + break; + + case kQueryTemplateNode: + PRINT(("query template\n")); + break; + + case kExecutableNode: + PRINT(("exe\n")); + break; + + case kDirectoryNode: + PRINT(("dir\n")); + break; + + case kLinkNode: + PRINT(("link\n")); + break; + + case kRootNode: + PRINT(("root\n")); + break; + + case kVolumeNode: + PRINT(("volume, name %s\n", fVolumeName ? fVolumeName : "")); + break; + + default: + PRINT(("unknown\n")); + break; + } + + if (level < 1) + return; + + if (!IsVolume()) + PRINT(("preferred app %s\n", fPreferredAppName ? fPreferredAppName : "")); + + PRINT(("icon from: ")); + switch (IconFrom()) { + case kUnknownSource: + PRINT(("unknown\n")); + break; + case kUnknownNotFromNode: + PRINT(("unknown but not from a node\n")); + break; + case kTrackerDefault: + PRINT(("tracker default\n")); + break; + case kTrackerSupplied: + PRINT(("tracker supplied\n")); + break; + case kMetaMime: + PRINT(("metamime\n")); + break; + case kPreferredAppForType: + PRINT(("preferred app for type\n")); + break; + case kPreferredAppForNode: + PRINT(("preferred app for node\n")); + break; + case kNode: + PRINT(("node\n")); + break; + case kVolume: + PRINT(("volume\n")); + break; + } + + PRINT(("model %s opened %s \n", !IsNodeOpen() ? "not " : "", + IsNodeOpenForWriting() ? "for writing" : "")); + + if (IsNodeOpen()) { + node_ref nodeRef; + fNode->GetNodeRef(&nodeRef); + PRINT(("node ref of open Node %Lx %x\n", nodeRef.node, nodeRef.device)); + } + + if (deep && IsSymLink()) { + BEntry tmpEntry(EntryRef(), true); + Model tmp(&tmpEntry); + PRINT(("symlink to:\n")); + tmp.PrintToStream(); + } + TrackIconSource(B_MINI_ICON); + TrackIconSource(B_LARGE_ICON); +} + + +void +Model::TrackIconSource(icon_size size) +{ + PRINT(("tracking %s icon\n", size == B_LARGE_ICON ? "large" : "small")); + BRect rect; + if (size == B_MINI_ICON) + rect.Set(0, 0, B_MINI_ICON - 1, B_MINI_ICON - 1); + else + rect.Set(0, 0, B_LARGE_ICON - 1, B_LARGE_ICON - 1); + + BBitmap bitmap(rect, B_COLOR_8_BIT); + + BModelOpener opener(this); + + if (Node() == NULL) { + PRINT(("track icon error - no node\n")); + return; + } + + if (IsSymLink()) { + PRINT(("tracking symlink icon\n")); + if (fLinkTo) { + fLinkTo->TrackIconSource(size); + return; + } + } + + if (fBaseType == kVolumeNode) { + BVolume volume(NodeRef()->device); + status_t result = volume.GetIcon(&bitmap, size); + PRINT(("getting icon from volume %s\n", strerror(result))); + } else { + BNodeInfo nodeInfo(Node()); + + status_t err = nodeInfo.GetIcon(&bitmap, size); + if (err == B_OK) { + // file knew which icon to use, we are done + PRINT(("track icon - got icon from file\n")); + return; + } + + char preferredApp[B_MIME_TYPE_LENGTH]; + err = nodeInfo.GetPreferredApp(preferredApp); + if (err == B_OK && preferredApp[0]) { + BMimeType preferredAppType(preferredApp); + err = preferredAppType.GetIconForType(MimeType(), &bitmap, size); + if (err == B_OK) { + PRINT(("track icon - got icon for type %s from preferred app %s for file\n", + MimeType(), preferredApp)); + return; + } + } + + BMimeType mimeType(MimeType()); + err = mimeType.GetIcon(&bitmap, size); + if (err == B_OK) { + // the system knew what icon to use for the type, we are done + PRINT(("track icon - signature %s, got icon from system\n", + MimeType())); + return; + } + + err = mimeType.GetPreferredApp(preferredApp); + if (err != B_OK) { + // no preferred App for document, give up + PRINT(("track icon - signature %s, no prefered app, error %s\n", + MimeType(), strerror(err))); + return; + } + + BMimeType preferredAppType(preferredApp); + err = preferredAppType.GetIconForType(MimeType(), &bitmap, size); + if (err == B_OK) { + // the preferred app knew icon to use for the type, we are done + PRINT(("track icon - signature %s, got icon from preferred app %s\n", + MimeType(), preferredApp)); + return; + } + PRINT(("track icon - signature %s, preferred app %s, no icon, error %s\n", + MimeType(), preferredApp, strerror(err))); + } +} + +#endif // DEBUG + +#ifdef CHECK_OPEN_MODEL_LEAKS + +namespace BPrivate { +#include + +void +DumpOpenModels(bool extensive) +{ + if (readOnlyOpenModelList) { + int32 count = readOnlyOpenModelList->CountItems(); + printf("%ld models open read-only:\n", count); + printf("==========================\n"); + for (int32 index = 0; index < count; index++) { + if (extensive) { + printf("---------------------------\n"); + readOnlyOpenModelList->ItemAt(index)->PrintToStream(); + } else + printf("%s\n", readOnlyOpenModelList->ItemAt(index)->Name()); + } + } + if (writableOpenModelList) { + int32 count = writableOpenModelList->CountItems(); + printf("%ld models open writable:\n", count); + printf("models open writable:\n"); + printf("======================\n"); + for (int32 index = 0; index < count; index++) { + if (extensive) { + printf("---------------------------\n"); + writableOpenModelList->ItemAt(index)->PrintToStream(); + } else + printf("%s\n", writableOpenModelList->ItemAt(index)->Name()); + } + } +} + + +void +InitOpenModelDumping() +{ + readOnlyOpenModelList = 0; + writableOpenModelList = 0; +} + +} // namespace BPrivate + +#endif // CHECK_OPEN_MODEL_LEAKS diff --git a/src/kits/tracker/Model.h b/src/kits/tracker/Model.h new file mode 100644 index 0000000000..6a11834e39 --- /dev/null +++ b/src/kits/tracker/Model.h @@ -0,0 +1,473 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// Dedicated to BModel + +#ifndef _NU_MODEL_H +#define _NU_MODEL_H + +#include +#include +#include +#include + +#include "IconCache.h" +#include "ObjectList.h" + + +class BPath; +class BHandler; +class BEntry; +class BQuery; + +#if __GNUC__ && __GNUC__ < 3 +// using std::stat instead of just stat here because of what +// seems to be a gcc bug involving namespace and struct stat interaction +typedef struct std::stat StatStruct; +#else +// on mwcc std isn't turned on but there is no bug either. +// Also seems to be fixed in gcc 3. +typedef struct stat StatStruct; +#endif + +namespace BPrivate { + +enum { + kDoesNotSupportType, + kSuperhandlerModel, + kModelSupportsSupertype, + kModelSupportsType, + kModelSupportsFile +}; + +class Model { + public: + Model(); + Model(const Model &); + Model(const BEntry *entry, bool open = false, bool writable = false); + Model(const entry_ref *, bool traverse = false, bool open = false, + bool writable = false); + Model(const node_ref *dirNode, const node_ref *node, const char *name, + bool open = false, bool writable = false); + ~Model(); + + Model& operator=(const Model &); + + status_t InitCheck() const; + + status_t SetTo(const BEntry *, bool open = false, bool writable = false); + status_t SetTo(const entry_ref *, bool traverse = false, bool open = false, + bool writable = false); + status_t SetTo(const node_ref *dirNode, const node_ref *node, const char *name, + bool open = false, bool writable = false); + + int CompareFolderNamesFirst(const Model *compareModel) const; + + // node management + status_t OpenNode(bool writable = false); + // also used to switch from read-only to writable + void CloseNode(); + bool IsNodeOpen() const; + bool IsNodeOpenForWriting() const; + + status_t UpdateStatAndOpenNode(bool writable = false); + // like OpenNode, called on zombie poses to check if they turned + // real, starts by rereading the stat structure + + // basic getters + const char *Name() const; + const entry_ref *EntryRef() const; + const node_ref *NodeRef() const; + const StatStruct *StatBuf() const; + + BNode *Node() const; + // returns null if not Open + void GetPath(BPath *) const; + void GetEntry(BEntry *) const; + + const char *MimeType() const; + const char *PreferredAppSignature() const; + // only not-null if not default for type and not self for app + void SetPreferredAppSignature(const char *); + + void GetPreferredAppForBrokenSymLink(BString &result); + // special purpose call - if a symlink is unresolvable, it makes sense + // to be able to get at it's preferred handler which may be different + // from the Tracker. Used by the network neighborhood. + + // type getters + bool IsFile() const; + bool IsDirectory() const; + bool IsQuery() const; + bool IsQueryTemplate() const; + bool IsContainer() const; + bool IsExecutable() const; + bool IsSymLink() const; + bool IsRoot() const; + bool IsVolume() const; + + IconSource IconFrom() const; + void SetIconFrom(IconSource); + // where is this model getting it's icon from + + void ResetIconFrom(); + // called from the attribute changed calls to force a lookup of + // a new icon + + // symlink handling calls, mainly used by the IconCache + const Model *ResolveIfLink() const; + Model *ResolveIfLink(); + // works on anything + Model *LinkTo() const; + // fast, works only on symlinks + void SetLinkTo(Model *); + + status_t GetLongVersionString(BString &, version_kind); + status_t GetVersionString(BString &, version_kind); + status_t AttrAsString(BString &, int64 *value, const char *attributeName, + uint32 attributeType); + + // Node monitor update call + void UpdateEntryRef(const node_ref *dirRef, const char *name); + bool AttrChanged(const char *); + // returns true if pose needs to update it's icon, etc. + // pass null to force full update + bool StatChanged(); + // returns true if pose needs to update it's icon + + status_t WatchVolumeAndMountPoint(uint32, BHandler *); + // correctly handles boot volume name watching + + bool IsDropTarget(const Model *forDocument = 0, + bool traverse = false) const; + // if nonzero passed, mime info is used to + // resolve if document can be opened + // if zero, all executables, directories and volumes pass + // if traverse, dereference symlinks + bool IsDropTargetForList(const BObjectList *list) const; + // contains mime types of all documents about to be handled + // by model + + #if DEBUG + void PrintToStream(int32 level = 1, bool deep = false); + void TrackIconSource(icon_size); + #endif + + bool IsSuperHandler() const; + int32 SupportsMimeType(const char *type, const BObjectList *list, + bool exactReason = false) const; + // pass in one string in or a bunch in + // if false, returns as soon as it figures out that + // app supports a given type, if true, returns an exact reason + + // get rid of this?? + ssize_t WriteAttr(const char *attr, type_code type, off_t, + const void *buffer, size_t ); + // cover call, creates a writable node and writes out attributes + // into it; work around for file nodes not being writeable + ssize_t WriteAttrKillForegin(const char *attr, const char *foreignAttr, + type_code type, off_t, const void *buffer, size_t); + + bool Mimeset(bool force); + // returns true if mime type changed + private: + status_t OpenNodeCommon(bool writable); + void SetupBaseType(); + void FinishSettingUpType(); + void DeletePreferredAppVolumeNameLinkTo(); + + status_t FetchOneQuery(const BQuery *, BHandler *target, + BObjectList*, BVolume *); + + enum CanHandleResult { + kCanHandle, + kCannotHandle, + kNeedToCheckType + }; + + CanHandleResult CanHandleDrops() const; + + enum NodeType { + kPlainNode, + kExecutableNode, + kDirectoryNode, + kLinkNode, + kQueryNode, + kQueryTemplateNode, + kVolumeNode, + kRootNode, + kUnknownNode + }; + + entry_ref fEntryRef; + StatStruct fStatBuf; + BString fMimeType; // should use string that may be shared for common types + + // bit of overloading hackery here to save on footprint + union { + char *fPreferredAppName; // used if we are neither a volume nor a symlink + char *fVolumeName; // used if we are a volume + Model *fLinkTo; // used if we are a symlink + }; + + uint8 fBaseType; + uint8 fIconFrom; + bool fWritable; + BNode *fNode; + status_t fStatus; +}; + + +class ModelNodeLazyOpener { + // a utility open state manager, usefull to allocate on stack + // and have close up model when done, etc. + public: + // consider failing when open does not succeed + + ModelNodeLazyOpener(Model *model, bool writable = false, bool openLater = true); + ~ModelNodeLazyOpener(); + + bool IsOpen() const; + bool IsOpenForWriting() const; + bool IsOpen(bool forWriting) const; + Model *TargetModel() const; + status_t OpenNode(bool writable = false); + + private: + Model *fModel; + bool fWasOpen; + bool fWasOpenForWriting; +}; + +// handy flavors of openers +class BModelOpener : public ModelNodeLazyOpener { + public: + BModelOpener(Model *model) + : ModelNodeLazyOpener(model, false, false) + { + } +}; + +class BModelWriteOpener : public ModelNodeLazyOpener { + public: + BModelWriteOpener(Model *model) + : ModelNodeLazyOpener(model, true, false) + { + } +}; + + +#if DEBUG +// #define CHECK_OPEN_MODEL_LEAKS +#endif + +#ifdef CHECK_OPEN_MODEL_LEAKS +void DumpOpenModels(bool extensive); +void InitOpenModelDumping(); +#endif + +// inlines follow ----------------------------------- + +inline const char * +Model::MimeType() const +{ + return fMimeType.String(); +} + +inline const entry_ref * +Model::EntryRef() const +{ + return &fEntryRef; +} + +inline const node_ref * +Model::NodeRef() const +{ + // the stat structure begins with a node_ref + return (node_ref *)&fStatBuf; +} + +inline BNode * +Model::Node() const +{ + return fNode; +} + +inline const StatStruct * +Model::StatBuf() const +{ + return &fStatBuf; +} + +inline IconSource +Model::IconFrom() const +{ + return (IconSource)fIconFrom; +} + +inline void +Model::SetIconFrom(IconSource from) +{ + fIconFrom = from; +} + +inline Model * +Model::LinkTo() const +{ + ASSERT(IsSymLink()); + return fLinkTo; +} + +inline bool +Model::IsFile() const +{ + return fBaseType == kPlainNode + || fBaseType == kQueryNode + || fBaseType == kQueryTemplateNode + || fBaseType == kExecutableNode; +} + +inline bool +Model::IsVolume() const +{ + return fBaseType == kVolumeNode; +} + +inline bool +Model::IsDirectory() const +{ + return fBaseType == kDirectoryNode + || fBaseType == kVolumeNode + || fBaseType == kRootNode; +} + +inline bool +Model::IsQuery() const +{ + return fBaseType == kQueryNode; +} + +inline bool +Model::IsQueryTemplate() const +{ + return fBaseType == kQueryTemplateNode; +} + +inline bool +Model::IsContainer() const +{ + // I guess as in should show container window - + // volumes show the volume window + return IsQuery() || IsDirectory(); +} + +inline bool +Model::IsRoot() const +{ + return fBaseType == kRootNode; +} + +inline bool +Model::IsExecutable() const +{ + return fBaseType == kExecutableNode; +} + +inline bool +Model::IsSymLink() const +{ + return fBaseType == kLinkNode; +} + +inline +ModelNodeLazyOpener::ModelNodeLazyOpener(Model *model, bool writable, bool openLater) + : fModel(model), + fWasOpen(model->IsNodeOpen()), + fWasOpenForWriting(model->IsNodeOpenForWriting()) +{ + if (!openLater) + OpenNode(writable); +} + +inline +ModelNodeLazyOpener::~ModelNodeLazyOpener() +{ + if (!fModel->IsNodeOpen()) + return; + if (!fWasOpen) + fModel->CloseNode(); + else if (!fWasOpenForWriting) + fModel->OpenNode(); +} + +inline bool +ModelNodeLazyOpener::IsOpen() const +{ + return fModel->IsNodeOpen(); +} + +inline bool +ModelNodeLazyOpener::IsOpenForWriting() const +{ + return fModel->IsNodeOpenForWriting(); +} + +inline bool +ModelNodeLazyOpener::IsOpen(bool forWriting) const +{ + return forWriting ? fModel->IsNodeOpenForWriting() : fModel->IsNodeOpen(); +} + +inline Model * +ModelNodeLazyOpener::TargetModel() const +{ + return fModel; +} + +inline status_t +ModelNodeLazyOpener::OpenNode(bool writable) +{ + if (writable) { + if (!fModel->IsNodeOpenForWriting()) + return fModel->OpenNode(true); + } else if (!fModel->IsNodeOpen()) + return fModel->OpenNode(); + + return B_OK; +} + +} // namespace BPrivate + + +#endif diff --git a/src/kits/tracker/MountMenu.cpp b/src/kits/tracker/MountMenu.cpp new file mode 100644 index 0000000000..ffe0384a75 --- /dev/null +++ b/src/kits/tracker/MountMenu.cpp @@ -0,0 +1,210 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// MountMenu implements a context menu used for mounting/unmounting volumes + +#include +#include +#include +#include +#include +#include + +#include "AutoMounter.h" +#include "Commands.h" +#include "MountMenu.h" +#include "IconMenuItem.h" +#include "Tracker.h" +#include "Bitmaps.h" + +#if OPEN_TRACKER +#include "DeviceMap.h" +#else +#include +#endif + +#define SHOW_NETWORK_VOLUMES + +MountMenu::MountMenu(const char *name) + : BMenu(name) +{ + SetFont(be_plain_font); +} + + +#if _INCLUDES_CLASS_DEVICE_MAP +struct AddOneAsMenuItemParams { + BMenu *mountMenu; +}; + + +static Partition * +AddOnePartitionAsMenuItem(Partition *partition, void *castToParams) +{ + if (partition->Hidden()) + return NULL; + + AddOneAsMenuItemParams *params = (AddOneAsMenuItemParams *)castToParams; + BBitmap *icon = new BBitmap(BRect(0, 0, B_MINI_ICON - 1, B_MINI_ICON - 1), + B_COLOR_8_BIT); + get_device_icon(partition->GetDevice()->Name(), icon->Bits(), B_MINI_ICON); + + + const char *name = partition->GetDevice()->DisplayName(); + + if (!partition->GetDevice()->IsFloppy() || + partition->Mounted() == kMounted) { + if (*partition->VolumeName()) + name = partition->VolumeName(); + else if (*partition->Type()) + name = partition->Type(); + } + + BMessage *message = new BMessage; + + if (partition->Mounted() == kMounted) { + message->what = kUnmountVolume; + message->AddInt32("device_id", partition->VolumeDeviceID()); + } else { + message->what = kMountVolume; + + // + // Floppies have an ID of -1, because they don't have + // partition (and hence no parititon ID). + // + if (partition->GetDevice()->IsFloppy()) + message->AddInt32("id", kFloppyID); + else + message->AddInt32("id", partition->UniqueID()); + } + + BMenuItem *item = new IconMenuItem(name, message, icon); + if (partition->Mounted() == kMounted) + item->SetMarked(true); + + if (partition->Mounted() == kMounted) { + BVolume partVolume(partition->VolumeDeviceID()); + + BVolume bootVolume; + BVolumeRoster().GetBootVolume(&bootVolume); + if (partVolume == bootVolume) + item->SetEnabled(false); + } + + params->mountMenu->AddItem(item); + + return NULL; +} +#endif + + +bool +MountMenu::AddDynamicItem(add_state) +{ +#if _INCLUDES_CLASS_DEVICE_MAP + for (;;) { + BMenuItem *item = RemoveItem(0L); + if (item == NULL) + break; + delete item; + } + + AddOneAsMenuItemParams params; + params.mountMenu = this; + + AutoMounter *autoMounter = dynamic_cast(be_app)-> + AutoMounterLoop(); + + autoMounter->CheckVolumesNow(); + autoMounter->EachPartition(&AddOnePartitionAsMenuItem, ¶ms); + +#ifdef SHOW_NETWORK_VOLUMES + + // iterate the volume roster and look for volumes with the + // 'shared' attributes -- these same volumes will not be returned + // by the autoMounter because they do not show up in the /dev tree + BVolumeRoster volumeRoster; + BVolume volume; + bool needSeparator = false; + while (volumeRoster.GetNextVolume(&volume) == B_OK) { + if (volume.IsShared()) { + needSeparator = true; + BBitmap *icon = new BBitmap(BRect(0, 0, 15, 15), B_COLOR_8_BIT); + fs_info info; + if (fs_stat_dev(volume.Device(), &info) != B_OK) { + PRINT(("Cannot get mount menu item icon; bad device ID\n")); + delete icon; + continue; + } + // Use the shared icon instead of the device icon + if (get_device_icon(info.device_name, icon->Bits(), B_MINI_ICON) != B_OK) + GetTrackerResources()->GetIconResource(kResShareIcon, B_MINI_ICON, icon); + + BMessage *message = new BMessage(kUnmountVolume); + message->AddInt32("device_id", volume.Device()); + char volumeName[B_FILE_NAME_LENGTH]; + volume.GetName(volumeName); + + BMenuItem *item = new IconMenuItem(volumeName, message, icon); + item->SetMarked(true); + AddItem(item); + } + } +#endif + + AddSeparatorItem(); + + // add an option to rescan the scsii bus, etc. + BMenuItem *rescanItem = NULL; + if (modifiers() & B_SHIFT_KEY) { + rescanItem = new BMenuItem("Rescan Devices", new BMessage(kAutomounterRescan)); + AddItem(rescanItem); + } + + BMenuItem *mountAll = new BMenuItem("Mount All", new BMessage(kMountAllNow)); + AddItem(mountAll); + BMenuItem *mountSettings = new BMenuItem("Settings"B_UTF8_ELLIPSIS, + new BMessage(kRunAutomounterSettings)); + AddItem(mountSettings); + + SetTargetForItems(be_app); + + if (rescanItem) + rescanItem->SetTarget(autoMounter); + + return false; +#else + return true; +#endif +} diff --git a/src/kits/tracker/MountMenu.h b/src/kits/tracker/MountMenu.h new file mode 100644 index 0000000000..a3d28b7d51 --- /dev/null +++ b/src/kits/tracker/MountMenu.h @@ -0,0 +1,57 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef MOUNTMENU_H +#define MOUNTMENU_H + +#include + +namespace BPrivate { + +class MountMenu : public BMenu { +public: + + MountMenu(const char *); + +protected: + + virtual bool AddDynamicItem(add_state); + +}; + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/NavMenu.cpp b/src/kits/tracker/NavMenu.cpp new file mode 100644 index 0000000000..6b5ed62c76 --- /dev/null +++ b/src/kits/tracker/NavMenu.cpp @@ -0,0 +1,851 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// NavMenu is a hierarchical menu of volumes, folders, files and queries +// displays icons, uses the SlowMenu API for full interruptability + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Attributes.h" +#include "Commands.h" +#include "ContainerWindow.h" +#include "DesktopPoseView.h" +#include "Tracker.h" +#include "FSUtils.h" +#include "IconMenuItem.h" +#include "MimeTypes.h" +#include "NavMenu.h" +#include "PoseView.h" +#include "Thread.h" +#include "FunctionObject.h" +#include "QueryPoseView.h" + + +namespace BPrivate { + +const int32 kMinMenuWidth = 150; + +enum nav_flags { + kVolumesOnly = 1, + kShowParent = 2 +}; + + +bool +SpringLoadedFolderCompareMessages(const BMessage *incoming, const BMessage *dragmessage) +{ + if (!dragmessage || !incoming) + return false; + + bool retvalue=false; + for (int32 inIndex=0; incoming->HasRef("refs", inIndex); inIndex++) { + entry_ref inRef; + if (incoming->FindRef("refs", inIndex, &inRef) != B_OK) { + retvalue = false; + break; + } + + bool inRefMatch = false; + for (int32 dragIndex=0; dragmessage->HasRef("refs", dragIndex); dragIndex++) { + entry_ref dragRef; + if (dragmessage->FindRef("refs", dragIndex, &dragRef) != B_OK) { + inRefMatch = false; + break; + } + // if the incoming ref matches any ref in the drag ref + // then we can try the next incoming ref + if (inRef == dragRef) { + inRefMatch = true; + break; + } + } + retvalue = inRefMatch; + if (!inRefMatch) + break; + } + + if (retvalue) { + // if all the refs match + // try and see if this is another instance of the same + // drag contents, but new drag + retvalue = false; + BPoint inPt, dPt; + if (incoming->FindPoint("click_pt", &inPt) == B_OK) + if (dragmessage->FindPoint("click_pt", &dPt) == B_OK) + retvalue = (inPt == dPt); + } + + return retvalue; +} + + +void +SpringLoadedFolderSetMenuStates(const BMenu* menu, const BObjectList *typeslist) +{ + if (!menu || !typeslist) + return; + + // if a types list exists + // iterate through the list and see if each item + // can support any item in the list + // set the enabled state of the item + int32 count = menu->CountItems(); + for (int32 index = 0 ; index < count ; index++) { + ModelMenuItem *item = dynamic_cast(menu->ItemAt(index)); + if (!item) + continue; + + const Model *model = item->TargetModel(); + if (!model) + continue; + + if (model->IsSymLink()) { + // find out what the model is, resolve if symlink + BEntry entry(model->EntryRef(), true); + if (entry.InitCheck() == B_OK) { + if (entry.IsDirectory()) { + // folder? always keep enabled + item->SetEnabled(true); + } else { + // other, check its support + Model resolvedModel(&entry); + int32 supported = resolvedModel.SupportsMimeType(NULL, typeslist); + item->SetEnabled(supported != kDoesNotSupportType); + } + } else + // bad entry ref (bad symlink?), disable + item->SetEnabled(false); + } else if (model->IsDirectory() || model->IsRoot() || model->IsVolume()) + // always enabled if a container + item->SetEnabled(true); + else if (model->IsFile() || model->IsExecutable()) { + int32 supported = model->SupportsMimeType(NULL, typeslist); + item->SetEnabled(supported != kDoesNotSupportType); + } else + item->SetEnabled(false); + } +} + + +void +SpringLoadedFolderAddUniqueTypeToList(entry_ref *ref, BObjectList *typeslist) +{ + if (!ref || !typeslist) + return; + + // get the mime type for the current ref + BNodeInfo nodeinfo; + BNode node(ref); + if (node.InitCheck() != B_OK) + return; + + nodeinfo.SetTo(&node); + + char mimestr[B_MIME_TYPE_LENGTH]; + // add it to the list + if (nodeinfo.GetType(mimestr) == B_OK && strlen(mimestr) > 0) { + // if this is a symlink, add symlink to the list (below) + // resolve the symlink, add the resolved type + // to the list + if (strcmp(B_LINK_MIMETYPE, mimestr) == 0) { + BEntry entry(ref, true); + if (entry.InitCheck() == B_OK) { + entry_ref resolvedRef; + if (entry.GetRef(&resolvedRef) == B_OK) + SpringLoadedFolderAddUniqueTypeToList(&resolvedRef, typeslist); + } + } + // scan the current list, don't add dups + bool unique = true; + int32 count = typeslist->CountItems(); + for (int32 index = 0 ; index < count ; index++) { + if (typeslist->ItemAt(index)->Compare(mimestr) == 0) { + unique = false; + break; + } + } + + if (unique) + typeslist->AddItem(new BString(mimestr)); + } +} + + +void +SpringLoadedFolderCacheDragData(const BMessage *incoming, BMessage **message, BObjectList **typeslist) +{ + if (!incoming) + return; + + delete *message; + delete *typeslist; + + BMessage *localMessage = new BMessage(*incoming); + BObjectList *localTypesList = new BObjectList(10, true); + + for (int32 index=0; incoming->HasRef("refs", index); index++) { + entry_ref ref; + if (incoming->FindRef("refs", index, &ref) != B_OK) + continue; + + SpringLoadedFolderAddUniqueTypeToList(&ref, localTypesList); + } + + *message = localMessage; + *typeslist = localTypesList; +} + +} + + +// #pragma mark - + + +BNavMenu::BNavMenu(const char *title, uint32 message, const BHandler *target, + BWindow *parentWindow, const BObjectList *list) + : BSlowMenu(title), + fMessage(message), + fMessenger(target, target->Looper()), + fParentWindow(parentWindow), + fFlags(0), + fItemList(0), + fContainer(0), + fTypesList(list) +{ + InitIconPreloader(); + + SetFont(be_plain_font); + + // add the parent window to the invocation message so that it + // can be closed if option modifier held down during invocation + BContainerWindow *originatingWindow = dynamic_cast(fParentWindow); + if (originatingWindow) + fMessage.AddData("nodeRefsToClose", B_RAW_TYPE, + originatingWindow->TargetModel()->NodeRef(), sizeof (node_ref)); + + // too long to have triggers + SetTriggersEnabled(false); +} + + +BNavMenu::BNavMenu(const char *title, uint32 message, const BMessenger &messenger, + BWindow *parentWindow, const BObjectList *list) + : BSlowMenu(title), + fMessage(message), + fMessenger(messenger), + fParentWindow(parentWindow), + fFlags(0), + fItemList(0), + fContainer(0), + fTypesList(list) +{ + InitIconPreloader(); + + SetFont(be_plain_font); + + // add the parent window to the invocation message so that it + // can be closed if option modifier held down during invocation + BContainerWindow *originatingWindow = dynamic_cast(fParentWindow); + if (originatingWindow) + fMessage.AddData("nodeRefsToClose", B_RAW_TYPE, + originatingWindow->TargetModel()->NodeRef(), sizeof (node_ref)); + + // too long to have triggers + SetTriggersEnabled(false); +} + + +BNavMenu::~BNavMenu() +{ +} + + +void +BNavMenu::AttachedToWindow() +{ + BSlowMenu::AttachedToWindow(); + + SpringLoadedFolderSetMenuStates(this, fTypesList); + // if dragging (fTypesList != NULL) + // set the menu items enabled state + // relative to the ability to handle an item in the + // drag message + ResetTargets(); + // allow an opportunity to reset the target for each of the items +} + + +void +BNavMenu::DetachedFromWindow() +{ + // does this need to set this to null? + // the parent, handling dnd should set this + // appropriately + // + // if this changes, BeMenu and RecentsMenu + // in Deskbar should also change + fTypesList = NULL; +} + + +void +BNavMenu::ResetTargets() +{ + SetTargetForItems(Target()); +} + + +void +BNavMenu::ForceRebuild() +{ + ClearMenuBuildingState(); + fMenuBuilt = false; +} + + +bool +BNavMenu::NeedsToRebuild() const +{ + return !fMenuBuilt; +} + + +void +BNavMenu::SetNavDir(const entry_ref *ref) +{ + ForceRebuild(); + // reset the slow menu building mechanism so we can add more stuff + + fNavDir = *ref; +} + + +void +BNavMenu::ClearMenuBuildingState() +{ + delete fContainer; + fContainer = NULL; + + // item list is non-owning, need to delete the items because + // they didn't get added to the menu + if (fItemList) { + int32 count = fItemList->CountItems(); + for (int32 index = count - 1; index >= 0; index--) + delete RemoveItem(index); + delete fItemList; + fItemList = NULL; + } +} + + +bool +BNavMenu::StartBuildingItemList() +{ + BEntry entry; + + if (fNavDir.device < 0 || entry.SetTo(&fNavDir) != B_OK + || !entry.Exists()) + return false; + + fItemList = new BObjectList(50); + + fIteratingDesktop = false; + + BDirectory parent; + status_t status = entry.GetParent(&parent); + + // if ref is the root item then build list of volume root dirs + fFlags = uint8((fFlags & ~kVolumesOnly) | (status == B_ENTRY_NOT_FOUND ? kVolumesOnly : 0)); + if (fFlags & kVolumesOnly) + return true; + + Model startModel(&entry, true); + if (startModel.InitCheck() != B_OK || !startModel.IsContainer()) + return false; + + if (startModel.IsQuery()) + fContainer = new QueryEntryListCollection(&startModel); + else if (FSIsDeskDir(&entry)) { + fIteratingDesktop = true; + fContainer = DesktopPoseView::InitDesktopDirentIterator(0, startModel.EntryRef()); + AddRootItemsIfNeeded(); + } else if (FSIsTrashDir(&entry)) { + // the trash window needs to display a union of all the + // trash folders from all the mounted volumes + BVolumeRoster volRoster; + volRoster.Rewind(); + BVolume volume; + fContainer = new EntryIteratorList(); + + while (volRoster.GetNextVolume(&volume) == B_OK) { + if (!volume.IsPersistent()) + continue; + + BDirectory trashDir; + + if (FSGetTrashDir(&trashDir, volume.Device()) == B_OK) + dynamic_cast(fContainer)-> + AddItem(new DirectoryEntryList(trashDir)); + } + } else + fContainer = new DirectoryEntryList(*dynamic_cast + (startModel.Node())); + + if (fContainer == NULL || fContainer->InitCheck() != B_OK) + return false; + + fContainer->Rewind(); + + return true; +} + + +void +BNavMenu::AddRootItemsIfNeeded() +{ + BVolumeRoster roster; + roster.Rewind(); + BVolume volume; + while (roster.GetNextVolume(&volume) == B_OK) { + + BDirectory root; + BEntry entry; + if (!volume.IsPersistent() + || volume.GetRootDirectory(&root) != B_OK + || root.GetEntry(&entry) != B_OK) + continue; + + Model model(&entry); + AddOneItem(&model); + } +} + + +bool +BNavMenu::AddNextItem() +{ + if (fFlags & kVolumesOnly) { + BuildVolumeMenu(); + return false; + } + + // limit nav menus to 500 items only + if (fItemList->CountItems() > 500) + return false; + + BEntry entry; + if (fContainer->GetNextEntry(&entry) != B_OK) { + // we're finished + return false; + } + + if (TrackerSettings().HideDotFiles()) { + char name[B_FILE_NAME_LENGTH]; + if (entry.GetName(name) == B_OK && name[0] == '.') + return true; + } + + Model model(&entry, true); + if (model.InitCheck() != B_OK) { +// PRINT(("not showing hidden item %s, wouldn't open\n", model->Name())); + return true; + } + + QueryEntryListCollection *queryContainer + = dynamic_cast(fContainer); + if (queryContainer && !queryContainer->ShowResultsFromTrash() + && FSInTrashDir(model.EntryRef())) { + // query entry is in trash and shall not be shown + return true; + } + + ssize_t size = -1; + PoseInfo poseInfo; + + if (model.Node()) + size = model.Node()->ReadAttr(kAttrPoseInfo, B_RAW_TYPE, 0, + &poseInfo, sizeof(poseInfo)); + + model.CloseNode(); + + // item might be in invisible + // ToDo: + // use more of PoseView's filtering here + if ((size == sizeof(poseInfo) + && !BPoseView::PoseVisible(&model, &poseInfo, false)) + || (fIteratingDesktop && !ShouldShowDesktopPose(fNavDir.device, + &model, &poseInfo))) { +// PRINT(("not showing hidden item %s\n", model.Name())); + return true; + } + + AddOneItem(&model); + return true; +} + + +void +BNavMenu::AddOneItem(Model *model) +{ + BMenuItem *item = NewModelItem(model, &fMessage, fMessenger, false, + dynamic_cast(fParentWindow), + fTypesList, &fTrackingHook); + + if (item) + fItemList->AddItem(item); +} + + +ModelMenuItem * +BNavMenu::NewModelItem(Model *model, const BMessage *invokeMessage, + const BMessenger &target, bool suppressFolderHierarchy, + BContainerWindow *parentWindow, const BObjectList *typeslist, + TrackingHookData *hook) +{ + if (model->InitCheck() != B_OK) + return 0; + entry_ref ref; + bool container = false; + if (model->IsSymLink()) { + + Model *newResolvedModel = 0; + Model *result = model->LinkTo(); + + if (!result) { + newResolvedModel = new Model(model->EntryRef(), true, true); + + if (newResolvedModel->InitCheck() != B_OK) { + // broken link, still can show though, bail + delete newResolvedModel; + result = 0; + } else + result = newResolvedModel; + } + + if (result) { + BModelOpener opener(result); + // open the model, if it ain't open already + + PoseInfo poseInfo; + ssize_t size = -1; + + if (result->Node()) + size = result->Node()->ReadAttr(kAttrPoseInfo, B_RAW_TYPE, 0, + &poseInfo, sizeof(poseInfo)); + + result->CloseNode(); + + if (size == sizeof(poseInfo) && !BPoseView::PoseVisible(result, + &poseInfo, false)) { + // link target sez it doesn't want to be visible, + // don't show the link + PRINT(("not showing hidden item %s\n", model->Name())); + delete newResolvedModel; + return 0; + } + ref = *result->EntryRef(); + container = result->IsContainer(); + } + model->SetLinkTo(result); + } else { + ref = *model->EntryRef(); + container = model->IsContainer(); + } + + BMessage *message = new BMessage(*invokeMessage); + message->AddRef("refs", model->EntryRef()); + + // Truncate the name if necessary + BString truncatedString(model->Name()); + be_plain_font->TruncateString(&truncatedString, B_TRUNCATE_END, + GetMaxMenuWidth()); + + ModelMenuItem *item = NULL; + if (!container || suppressFolderHierarchy) { + item = new ModelMenuItem(model, truncatedString.String(), message); + if (invokeMessage->what != B_REFS_RECEIVED) + item->SetEnabled(false); + // the above is broken for FavoritesMenu::AddNextItem, which uses a + // workaround - should fix this + } else { + BNavMenu *menu = new BNavMenu(truncatedString.String(), + invokeMessage->what, target, parentWindow, typeslist); + + menu->SetNavDir(&ref); + if (hook) + menu->InitTrackingHook(hook->fTrackingHook, &(hook->fTarget), + hook->fDragMessage); + + item = new ModelMenuItem(model, menu); + item->SetMessage(message); + } + + return item; +} + + +void +BNavMenu::BuildVolumeMenu() +{ + BVolumeRoster roster; + BVolume volume; + + roster.Rewind(); + while (roster.GetNextVolume(&volume) == B_OK) { + + if (!volume.IsPersistent()) + continue; + + BDirectory startDir; + if (volume.GetRootDirectory(&startDir) == B_OK) { + BEntry entry; + startDir.GetEntry(&entry); + + Model *model = new Model(&entry); + if (model->InitCheck() != B_OK) { + delete model; + continue; + } + + BNavMenu *menu = new BNavMenu(model->Name(), fMessage.what, + fMessenger, fParentWindow, fTypesList); + + menu->SetNavDir(model->EntryRef()); + + ASSERT(menu->Name()); + + ModelMenuItem *item = new ModelMenuItem(model, menu); + BMessage *message = new BMessage(fMessage); + + message->AddRef("refs", model->EntryRef()); + + item->SetMessage(message); + fItemList->AddItem(item); + ASSERT(item->Label()); + + } + } +} + + +int +BNavMenu::CompareFolderNamesFirstOne(const BMenuItem *i1, const BMenuItem *i2) +{ + const ModelMenuItem *item1 = dynamic_cast(i1); + const ModelMenuItem *item2 = dynamic_cast(i2); + + if (item1 != NULL && item2 != NULL) + return item1->TargetModel()->CompareFolderNamesFirst(item2->TargetModel()); + + return strcasecmp(i1->Label(), i2->Label()); +} + + +int +BNavMenu::CompareOne(const BMenuItem *i1, const BMenuItem *i2) +{ + return strcasecmp(i1->Label(), i2->Label()); +} + + +void +BNavMenu::DoneBuildingItemList() +{ + // add sorted items to menu + if (TrackerSettings().SortFolderNamesFirst()) + fItemList->SortItems(CompareFolderNamesFirstOne); + else + fItemList->SortItems(CompareOne); + + // if the parent link should be shown, it will be the first + // entry in the menu - but don't add the item if we're already + // at the file system's root + if (fFlags & kShowParent) { + BDirectory directory(&fNavDir); + BEntry entry(&fNavDir); + if (!directory.IsRootDirectory() + && entry.GetParent(&entry) == B_OK) { + Model model(&entry, true); + BLooper *looper; + AddNavParentDir(&model,fMessage.what,fMessenger.Target(&looper)); + } + } + + int32 count = fItemList->CountItems(); + for (int32 index = 0; index < count; index++) + AddItem(fItemList->ItemAt(index)); + fItemList->MakeEmpty(); + + if (!count) { + BMenuItem *item = new BMenuItem("Empty Folder", 0); + item->SetEnabled(false); + AddItem(item); + } + + SetTargetForItems(fMessenger); +} + + +int32 +BNavMenu::GetMaxMenuWidth(void) +{ + int32 width = (int32)(BScreen().Frame().Width() / 4); + return (width < kMinMenuWidth) ? kMinMenuWidth : width; +} + + +void +BNavMenu::AddNavDir(const Model *model, uint32 what, BHandler *target, + bool populateSubmenu) +{ + BMessage *message = new BMessage((uint32)what); + message->AddRef("refs", model->EntryRef()); + ModelMenuItem *item = NULL; + + if (populateSubmenu) { + BNavMenu *navMenu = new BNavMenu(model->Name(), what, target); + navMenu->SetNavDir(model->EntryRef()); + navMenu->InitTrackingHook(fTrackingHook.fTrackingHook, &(fTrackingHook.fTarget), + fTrackingHook.fDragMessage); + item = new ModelMenuItem(model, navMenu); + item->SetMessage(message); + } else + item = new ModelMenuItem(model, model->Name(), message); + + AddItem(item); +} + + +void +BNavMenu::AddNavParentDir(const char *name,const Model *model,uint32 what,BHandler *target) +{ + BNavMenu *menu = new BNavMenu(name,what,target); + menu->SetNavDir(model->EntryRef()); + menu->SetShowParent(true); + menu->InitTrackingHook(fTrackingHook.fTrackingHook, &(fTrackingHook.fTarget), + fTrackingHook.fDragMessage); + + BMenuItem *item = new SpecialModelMenuItem(model,menu); + + BMessage *message = new BMessage(what); + message->AddRef("refs",model->EntryRef()); + item->SetMessage(message); + + AddItem(item); +} + + +void +BNavMenu::AddNavParentDir(const Model *model, uint32 what, BHandler *target) +{ + AddNavParentDir("parent folder",model,what,target); +} + + +void +BNavMenu::SetShowParent(bool show) +{ + fFlags = uint8((fFlags & ~kShowParent) | (show ? kShowParent : 0)); +} + + +void +BNavMenu::SetTypesList(const BObjectList *list) +{ + fTypesList = list; +} + + +const BObjectList * +BNavMenu::TypesList() const +{ + return fTypesList; +} + + +void +BNavMenu::SetTarget(const BMessenger &msngr) +{ + fMessenger = msngr; +} + + +BMessenger +BNavMenu::Target() +{ + return fMessenger; +} + + +TrackingHookData * +BNavMenu::InitTrackingHook(bool (*hook)(BMenu *, void *), const BMessenger *target, + const BMessage *dragMessage) +{ + fTrackingHook.fTrackingHook = hook; + if (target) + fTrackingHook.fTarget = *target; + fTrackingHook.fDragMessage = dragMessage; + SetTrackingHookDeep(this, hook, &fTrackingHook); + return &fTrackingHook; +} + + +void +BNavMenu::SetTrackingHookDeep(BMenu *menu, bool (*func)(BMenu *, void *), void *state) +{ + menu->SetTrackingHook(func, state); + int32 count = menu->CountItems(); + for (int32 index = 0 ; index < count; index++) { + BMenuItem *item = menu->ItemAt(index); + if (!item) + continue; + + BMenu *submenu = item->Submenu(); + if (submenu) + SetTrackingHookDeep(submenu, func, state); + } +} + diff --git a/src/kits/tracker/Navigator.cpp b/src/kits/tracker/Navigator.cpp new file mode 100644 index 0000000000..9cda578a46 --- /dev/null +++ b/src/kits/tracker/Navigator.cpp @@ -0,0 +1,393 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ +#include "Bitmaps.h" +#include "Commands.h" +#include "ContainerWindow.h" +#include "FSUtils.h" +#include "Model.h" +#include "Navigator.h" +#include "Tracker.h" +#include +#include +#include + +namespace BPrivate { + +static const int32 kMaxHistory = 32; + +static const rgb_color kBgColor = {220, 220, 220, 255}; +static const rgb_color kShineColor = {255, 255, 255, 255}; +static const rgb_color kHalfDarkColor = {200, 200, 200, 255}; +static const rgb_color kDarkColor = {166, 166, 166, 255}; + +} + +// BPictureButton() will crash when giving zero pointers, +// although we really want and have to set up the +// pictures when we can, e.g. on a AttachedToWindow. +static BPicture sPicture; + +BNavigatorButton::BNavigatorButton(BRect rect, const char *name, BMessage *message, + int32 resIDon, int32 resIDoff, int32 resIDdisabled) + : BPictureButton(rect, name, &sPicture, &sPicture, message), + fResIDOn(resIDon), + fResIDOff(resIDoff), + fResIDDisabled(resIDdisabled) +{ + // Clear to background color to + // avoid ugly border on click + SetViewColor(kBgColor); + SetHighColor(kBgColor); + SetLowColor(kBgColor); +} + +BNavigatorButton::~BNavigatorButton() +{ +} + +void +BNavigatorButton::AttachedToWindow() +{ + BBitmap *bmpOn = 0; + GetTrackerResources()->GetBitmapResource(B_MESSAGE_TYPE, fResIDOn, &bmpOn); + SetPicture(bmpOn, true, true); + delete bmpOn; + + BBitmap *bmpOff = 0; + GetTrackerResources()->GetBitmapResource(B_MESSAGE_TYPE, fResIDOff, &bmpOff); + SetPicture(bmpOff, true, false); + delete bmpOff; + + BBitmap *bmpDisabled = 0; + GetTrackerResources()->GetBitmapResource(B_MESSAGE_TYPE, fResIDDisabled, &bmpDisabled); + SetPicture(bmpDisabled, false, false); + SetPicture(bmpDisabled, false, true); + delete bmpDisabled; +} + +void +BNavigatorButton::SetPicture(BBitmap *bitmap, bool enabled, bool on) +{ + if (bitmap) { + BPicture picture; + BView view(bitmap->Bounds(), "", 0, 0); + AddChild(&view); + view.BeginPicture(&picture); + view.SetHighColor(kBgColor); + view.FillRect(view.Bounds()); + view.SetDrawingMode(B_OP_OVER); + view.DrawBitmap(bitmap, BPoint(0, 0)); + view.EndPicture(); + RemoveChild(&view); + if (enabled) + if (on) + SetEnabledOn(&picture); + else + SetEnabledOff(&picture); + else + if (on) + SetDisabledOn(&picture); + else + SetDisabledOff(&picture); + } +} + + +BNavigator::BNavigator(const Model *model, BRect rect, uint32 resizeMask) + : BView(rect, "Navigator", resizeMask, B_WILL_DRAW), + fBack(0), + fForw(0), + fUp(0), + fBackHistory(8, true), + fForwHistory(8, true) +{ + // Get initial path + model->GetPath(&fPath); + + SetViewColor(kBgColor); + + float top = 2 + (be_plain_font->Size() - 8) / 2; + + // Set up widgets + fBack = new BNavigatorButton(BRect(3, top, 21, top + 17), "Back", + new BMessage(kNavigatorCommandBackward), kResBackNavActiveSel, + kResBackNavActive, kResBackNavInactive); + fBack->SetEnabled(false); + AddChild(fBack); + + fForw = new BNavigatorButton(BRect(35, top, 53, top + 17), "Forw", + new BMessage(kNavigatorCommandForward), kResForwNavActiveSel, + kResForwNavActive, kResForwNavInactive); + fForw->SetEnabled(false); + AddChild(fForw); + + fUp = new BNavigatorButton(BRect(67, top, 84, top + 17), "Up", + new BMessage(kNavigatorCommandUp), kResUpNavActiveSel, + kResUpNavActive, kResUpNavInactive); + fUp->SetEnabled(false); + AddChild(fUp); + + fLocation = new BTextControl(BRect(97, 2, rect.Width() - 2, 21), + "Location", "", "", new BMessage(kNavigatorCommandLocation), + B_FOLLOW_LEFT_RIGHT); + fLocation->SetDivider(0); + AddChild(fLocation); + +} + +BNavigator::~BNavigator() +{ +} + +void +BNavigator::AttachedToWindow() +{ + // Inital setup of widget states + UpdateLocation(0, kActionSet); + + // All messages should arrive here + fBack->SetTarget(this); + fForw->SetTarget(this); + fUp->SetTarget(this); + fLocation->SetTarget(this); +} + +void +BNavigator::Draw(BRect) +{ + // Draws a beveled smooth border + BeginLineArray(4); + AddLine(Bounds().LeftTop(), Bounds().RightTop(), kShineColor); + AddLine(Bounds().LeftTop(), Bounds().LeftBottom() - BPoint(0, 1), kShineColor); + AddLine(Bounds().LeftBottom() - BPoint(-1, 1), Bounds().RightBottom() - BPoint(0, 1), kHalfDarkColor); + AddLine(Bounds().LeftBottom(), Bounds().RightBottom(), kDarkColor); + EndLineArray(); +} + +void +BNavigator::MessageReceived(BMessage *message) +{ + switch (message->what) { + case kNavigatorCommandBackward: + GoBackward((modifiers() & B_OPTION_KEY) == B_OPTION_KEY); + break; + + case kNavigatorCommandForward: + GoForward((modifiers() & B_OPTION_KEY) == B_OPTION_KEY); + break; + + case kNavigatorCommandUp: + GoUp((modifiers() & B_OPTION_KEY) == B_OPTION_KEY); + break; + + case kNavigatorCommandLocation: + GoTo(); + break; + + default: + { + // Catch any dropped refs and try + // to switch to this new directory + entry_ref ref; + if (message->FindRef("refs", &ref) == B_OK) { + BMessage message(kSwitchDirectory); + BEntry entry(&ref, true); + if (!entry.IsDirectory()) { + entry.GetRef(&ref); + BPath path(&ref); + path.GetParent(&path); + get_ref_for_path(path.Path(), &ref); + } + message.AddRef("refs", &ref); + message.AddInt32("action", kActionSet); + Window()->PostMessage(&message); + } + } + } +} + +void +BNavigator::GoBackward(bool option) +{ + int32 itemCount = fBackHistory.CountItems(); + if (itemCount >= 2 && fBackHistory.ItemAt(itemCount - 2)) { + BEntry entry; + if (entry.SetTo(fBackHistory.ItemAt(itemCount - 2)->Path()) == B_OK) + SendNavigationMessage(kActionBackward, &entry, option); + } +} + +void +BNavigator::GoForward(bool option) +{ + if (fForwHistory.CountItems() >= 1) { + BEntry entry; + if (entry.SetTo(fForwHistory.LastItem()->Path()) == B_OK) + SendNavigationMessage(kActionForward, &entry, option); + } +} + +void +BNavigator::GoUp(bool option) +{ + BEntry entry; + if (entry.SetTo(fPath.Path()) == B_OK) { + BEntry parentEntry; + if (entry.GetParent(&parentEntry) == B_OK && !FSIsDeskDir(&parentEntry)) + SendNavigationMessage(kActionUp, &parentEntry, option); + } +} + +void +BNavigator::SendNavigationMessage(NavigationAction action, BEntry *entry, bool option) +{ + entry_ref ref; + + if (entry->GetRef(&ref) == B_OK) { + BMessage message; + message.AddRef("refs", &ref); + message.AddInt32("action", action); + + // get the node of this folder for selecting it in the new location + const node_ref *nodeRef; + if (Window() && Window()->TargetModel()) + nodeRef = Window()->TargetModel()->NodeRef(); + else + nodeRef = NULL; + + // if the option key was held down, open in new window (send message to be_app) + // otherwise send message to this window. TTracker (be_app) understands nodeRefToSlection, + // BContainerWindow doesn't, so we have to select the item manually + if (option) { + message.what = B_REFS_RECEIVED; + if (nodeRef) + message.AddData("nodeRefToSelect", B_RAW_TYPE, nodeRef, sizeof(node_ref)); + be_app->PostMessage(&message); + } else { + message.what = kSwitchDirectory; + Window()->PostMessage(&message); + UnlockLooper(); + // This is to prevent a dead-lock situation. SelectChildInParentSoon() + // eventually locks the TaskLoop::fLock. Later, when StandAloneTaskLoop::Run() + // runs, it also locks TaskLoop::fLock and subsequently locks this window's looper. + // Therefore we can't call SelectChildInParentSoon with our Looper locked, + // because we would get different orders of locking (thus the risk of dead-locking). + // + // Todo: Change the locking behaviour of StandAloneTaskLoop::Run() and sub- + // sequently called functions. + if (nodeRef) + dynamic_cast(be_app)->SelectChildInParentSoon(&ref, nodeRef); + LockLooper(); + } + } +} + +void +BNavigator::GoTo() +{ + BString pathname = fLocation->Text(); + + if (pathname.Compare("") == 0) + pathname = "/"; + + BEntry entry; + entry_ref ref; + + if (entry.SetTo(pathname.String()) == B_OK + && !FSIsDeskDir(&entry) + && entry.GetRef(&ref) == B_OK) { + BMessage message(kSwitchDirectory); + message.AddRef("refs", &ref); + message.AddInt32("action", kActionLocation); + Window()->PostMessage(&message); + } else { + BPath path; + + if (Window() + && Window()->TargetModel()) { + Window()->TargetModel()->GetPath(&path); + fLocation->SetText(path.Path()); + } + } +} + +void +BNavigator::UpdateLocation(const Model *newmodel, int32 action) +{ + if (newmodel) + newmodel->GetPath(&fPath); + + + // Modify history according to commands + switch (action) { + case kActionBackward: + fForwHistory.AddItem(fBackHistory.RemoveItemAt(fBackHistory.CountItems()-1)); + break; + case kActionForward: + fBackHistory.AddItem(fForwHistory.RemoveItemAt(fForwHistory.CountItems()-1)); + break; + case kActionUpdatePath: + break; + default: + fForwHistory.MakeEmpty(); + fBackHistory.AddItem(new BPath(fPath)); + + for (;fBackHistory.CountItems()>kMaxHistory;) + fBackHistory.RemoveItem(fBackHistory.FirstItem(), true); + break; + } + + // Enable Up button when there is any parent + BEntry entry; + if (entry.SetTo(fPath.Path()) == B_OK) { + BEntry parentEntry; + fUp->SetEnabled(entry.GetParent(&parentEntry) == B_OK && !FSIsDeskDir(&parentEntry)); + } + + // Enable history buttons if history contains something + fForw->SetEnabled(fForwHistory.CountItems() > 0); + fBack->SetEnabled(fBackHistory.CountItems() > 1); + + // Avoid loss of selection and cursor position + if (action != kActionLocation) + fLocation->SetText(fPath.Path()); +} + +float +BNavigator::CalcNavigatorHeight(void) +{ + // Empiric formula from how much space the textview + // will take once it is attached (using be_plain_font): + return ceilf(11.0f + be_plain_font->Size()*(1.0f + 7.0f / 30.0f)); +} diff --git a/src/kits/tracker/Navigator.h b/src/kits/tracker/Navigator.h new file mode 100644 index 0000000000..1242324fa0 --- /dev/null +++ b/src/kits/tracker/Navigator.h @@ -0,0 +1,130 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ +#ifndef _NAVIGATOR_H_ +#define _NAVIGATOR_H_ + +#include "Model.h" + +#include +#include + +class BTextControl; +class BEntry; + +namespace BPrivate { + +enum NavigationAction +{ + kActionSet, + kActionForward, + kActionBackward, + kActionUp, + kActionLocation, + kActionUpdatePath, + + kNavigatorCommandBackward = 'NVBW', + kNavigatorCommandForward = 'NVFW', + kNavigatorCommandUp = 'NVUP', + kNavigatorCommandLocation = 'NVLC' +}; + +// Custom BPictureButton which takes +// bitmap resource IDs as arguments +class BNavigatorButton : public BPictureButton { +public: + BNavigatorButton(BRect rect, const char *name, BMessage *message, int32 resIDon, + int32 resIDoff, int32 resIDdisabled); + + ~BNavigatorButton(); + + virtual void AttachedToWindow(); + + void SetPicture(BBitmap *, bool enabled, bool on); + +private: + int32 fResIDOn; + int32 fResIDOff; + int32 fResIDDisabled; +}; + +class BNavigator : public BView { +public: + BNavigator(const Model *model, BRect rect, uint32 resizeMask = B_FOLLOW_LEFT_RIGHT); + ~BNavigator(); + + void UpdateLocation(const Model *newmodel, int32 action); + + static float CalcNavigatorHeight(void); + + BContainerWindow *Window() const; + +protected: + virtual void Draw(BRect rect); + virtual void MessageReceived(BMessage *msg); + virtual void AttachedToWindow(); + + void GoForward(bool option); // is option key held down? + void GoBackward(bool option); + void GoUp(bool option); + void SendNavigationMessage(NavigationAction, BEntry *, bool option); + + void GoTo(); + +private: + + BPath fPath; + BNavigatorButton *fBack; + BNavigatorButton *fForw; + BNavigatorButton *fUp; + BTextControl *fLocation; + + BObjectList fBackHistory; + BObjectList fForwHistory; + + typedef BView _inherited; +}; + +inline +BContainerWindow * +BNavigator::Window() const +{ + return dynamic_cast(_inherited::Window()); +} + + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/NodePreloader.cpp b/src/kits/tracker/NodePreloader.cpp new file mode 100644 index 0000000000..4a090a740a --- /dev/null +++ b/src/kits/tracker/NodePreloader.cpp @@ -0,0 +1,221 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// NodePreloader manages caching up icons from apps and prefs folder for +// fast display +// + +#include +#include +#include +#include +#include +#include +#include + +#include "AutoLock.h" +#include "IconCache.h" +#include "NodePreloader.h" +#include "Thread.h" +#include "Tracker.h" + + +NodePreloader * +NodePreloader::InstallNodePreloader(const char *name, BLooper *host) +{ + NodePreloader *result = new NodePreloader(name); + { + AutoLock lock(host); + if (!lock) + return NULL; + host->AddHandler(result); + } + result->Run(); + return result; +} + + +NodePreloader::NodePreloader(const char *name) + : BHandler(name), + fModelList(20, true), + fQuitRequested(false) +{ +} + + +NodePreloader::~NodePreloader() +{ + // block deletion while we are locked + fQuitRequested = true; + fLock.Lock(); +} + + +void +NodePreloader::Run() +{ + fLock.Lock(); + Thread::Launch(NewMemberFunctionObject(&NodePreloader::Preload, this)); +} + + +Model * +NodePreloader::FindModel(node_ref itemNode) const +{ + for (int32 count = fModelList.CountItems() - 1; count >= 0; count--) { + Model *model = fModelList.ItemAt(count); + if (*model->NodeRef() == itemNode) + return model; + } + return NULL; +} + + +void +NodePreloader::MessageReceived(BMessage *message) +{ + // respond to node monitor notifications + + node_ref itemNode; + switch (message->what) { + case B_NODE_MONITOR: + switch (message->FindInt32("opcode")) { + case B_ENTRY_REMOVED: + { + AutoLock locker(fLock); + message->FindInt32("device", &itemNode.device); + message->FindInt64("node", &itemNode.node); + Model *model = FindModel(itemNode); + if (!model) + break; +// PRINT(("preloader removing file %s\n", model->Name())); + IconCache::sIconCache->Removing(model); + fModelList.RemoveItem(model); + break; + } + + case B_ATTR_CHANGED: + case B_STAT_CHANGED: + { + AutoLock locker(fLock); + message->FindInt32("device", &itemNode.device); + message->FindInt64("node", &itemNode.node); + + const char *attrName; + message->FindString("attr", &attrName); + Model *model = FindModel(itemNode); + if (!model) + break; + BModelOpener opener(model); + IconCache::sIconCache->IconChanged(model->ResolveIfLink()); +// PRINT(("preloader updating file %s\n", model->Name())); + break; + } + } + break; + + default: + _inherited::MessageReceived(message); + break; + } +} + + +void +NodePreloader::PreloadOne(const char *dirPath) +{ +// PRINT(("preloading directory %s\n", dirPath)); + BDirectory dir(dirPath); + if (!dir.InitCheck() == B_OK) + return; + + node_ref nodeRef; + dir.GetNodeRef(&nodeRef); + + // have to node monitor the whole directory + TTracker::WatchNode(&nodeRef, B_WATCH_DIRECTORY, this); + + dir.Rewind(); + for (;;) { + entry_ref ref; + if (dir.GetNextRef(&ref) != B_OK) + break; + + BEntry entry(&ref); + if (!entry.IsFile()) + // only interrested in files + continue; + + Model *model = new Model(&ref, true); + if (model->InitCheck() == B_OK && model->IconFrom() == kUnknownSource) { + TTracker::WatchNode(model->NodeRef(), B_WATCH_STAT + | B_WATCH_ATTR, this); + IconCache::sIconCache->Preload(model, kNormalIcon, B_MINI_ICON, true); + fModelList.AddItem(model); + model->CloseNode(); + } else + delete model; + } + +} + + +void +NodePreloader::Preload() +{ + for (int32 count = 100; count >= 0; count--) { + // wait for a little bit before going ahead to reduce disk access contention + snooze(100000); + if (fQuitRequested) { + fLock.Unlock(); + return; + } + } + + BMessenger messenger(kTrackerSignature); + if (!messenger.IsValid()) { + // put out some message here! + return; + } + + ASSERT(fLock.IsLocked()); + BPath path; + if (find_directory(B_BEOS_APPS_DIRECTORY, &path) == B_OK) + PreloadOne(path.Path()); + if (find_directory(B_BEOS_PREFERENCES_DIRECTORY, &path) == B_OK) + PreloadOne(path.Path()); + + fLock.Unlock(); +} + diff --git a/src/kits/tracker/NodePreloader.h b/src/kits/tracker/NodePreloader.h new file mode 100644 index 0000000000..3de1c7c8cc --- /dev/null +++ b/src/kits/tracker/NodePreloader.h @@ -0,0 +1,85 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// Copyright 1997, 1998, Be Incorporated, All Rights Reserved. +// +// NodePreloader manages caching up icons from apps and prefs folder for +// fast display of the app/prefs nav menus +// +// Icons end up in the node cache as permanent entries -- to be able +// to do this, each entry has to be node monitored to avoid inode +// aliasing after a deletion, etc. +// +// The node preloader knows which icons to preload + +#ifndef __NODE_CACHE_PRELOADER__ +#define __NODE_CACHE_PRELOADER__ + +#include + +#include "ObjectList.h" +#include "Model.h" + +namespace BPrivate { + +class NodePreloader : public BHandler { +public: + static NodePreloader *InstallNodePreloader(const char *name, BLooper *host); + virtual ~NodePreloader(); + +protected: + NodePreloader(const char *name); + virtual void MessageReceived(BMessage *); + + void Run(); + +private: + void PreloadOne(const char *dirPath); + virtual void Preload(); + // for now just preload apps and prefs + Model *FindModel(node_ref) const; + + + BObjectList fModelList; + Benaphore fLock; + volatile bool fQuitRequested; + + typedef BHandler _inherited; +}; + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/NodeWalker.cpp b/src/kits/tracker/NodeWalker.cpp new file mode 100644 index 0000000000..bba97a47f8 --- /dev/null +++ b/src/kits/tracker/NodeWalker.cpp @@ -0,0 +1,697 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include +#include +#include +#include +#include + +#include "NodeWalker.h" + +namespace BTrackerPrivate { + +TWalker::~TWalker() +{ +} + +// all the following calls are pure viruals, should not get called +status_t +TWalker::GetNextEntry(BEntry *, bool ) +{ + TRESPASS(); + return B_ERROR; +} + +status_t +TWalker::GetNextRef(entry_ref *) +{ + TRESPASS(); + return B_ERROR; +} + +int32 +TWalker::GetNextDirents(struct dirent *, size_t, int32) +{ + TRESPASS(); + return 0; +} + + +status_t +TWalker::Rewind() +{ + TRESPASS(); + return B_ERROR; +} + +int32 +TWalker::CountEntries() +{ + TRESPASS(); + return -1; +} + + +TNodeWalker::TNodeWalker(bool includeTopDirectory) + : fDirs(20), + fTopIndex(-1), + fTopDir(0), + fIncludeTopDir(includeTopDirectory), + fOriginalIncludeTopDir(includeTopDirectory), + fJustFile(0), + fOriginalJustFile(0) +{ +} + + +TNodeWalker::TNodeWalker(const char *path, bool includeTopDirectory) + : fDirs(20), + fTopIndex(-1), + fTopDir(0), + fIncludeTopDir(includeTopDirectory), + fOriginalIncludeTopDir(includeTopDirectory), + fJustFile(0), + fOriginalDirCopy(path), + fOriginalJustFile(0) +{ + if (fOriginalDirCopy.InitCheck() != B_OK) { + // not a directory, set up walking a single file + fJustFile = new BEntry(path); + if (fJustFile->InitCheck() != B_OK) { + delete fJustFile; + fJustFile = NULL; + } + fOriginalJustFile = fJustFile; + } else { + fTopDir = new BDirectory(fOriginalDirCopy); + fTopIndex++; + fDirs.AddItem(fTopDir); + } +} + + +TNodeWalker::TNodeWalker(const entry_ref *ref, bool includeTopDirectory) + : fDirs(20), + fTopIndex(-1), + fTopDir(0), + fIncludeTopDir(includeTopDirectory), + fOriginalIncludeTopDir(includeTopDirectory), + fJustFile(0), + fOriginalDirCopy(ref), + fOriginalJustFile(0) +{ + if (fOriginalDirCopy.InitCheck() != B_OK) { + // not a directory, set up walking a single file + fJustFile = new BEntry(ref); + if (fJustFile->InitCheck() != B_OK) { + delete fJustFile; + fJustFile = NULL; + } + fOriginalJustFile = fJustFile; + } else { + fTopDir = new BDirectory(fOriginalDirCopy); + fTopIndex++; + fDirs.AddItem(fTopDir); + } +} + + +TNodeWalker::TNodeWalker(const BDirectory *dir, bool includeTopDirectory) + : fDirs(20), + fTopIndex(-1), + fTopDir(0), + fIncludeTopDir(includeTopDirectory), + fOriginalIncludeTopDir(includeTopDirectory), + fJustFile(0), + fOriginalDirCopy(*dir), + fOriginalJustFile(0) +{ + fTopDir = new BDirectory(*dir); + fTopIndex++; + fDirs.AddItem(fTopDir); +} + + +TNodeWalker::TNodeWalker() + : fDirs(20), + fTopIndex(-1), + fTopDir(0), + fIncludeTopDir(false), + fOriginalIncludeTopDir(false), + fJustFile(0), + fOriginalJustFile(0) +{ +} + +TNodeWalker::TNodeWalker(const char *path) + : fDirs(20), + fTopIndex(-1), + fTopDir(0), + fIncludeTopDir(false), + fOriginalIncludeTopDir(false), + fJustFile(0), + fOriginalDirCopy(path), + fOriginalJustFile(0) +{ + if (fOriginalDirCopy.InitCheck() != B_OK) { + // not a directory, set up walking a single file + fJustFile = new BEntry(path); + if (fJustFile->InitCheck() != B_OK) { + delete fJustFile; + fJustFile = NULL; + } + fOriginalJustFile = fJustFile; + } else { + fTopDir = new BDirectory(fOriginalDirCopy); + fTopIndex++; + fDirs.AddItem(fTopDir); + } +} + +TNodeWalker::TNodeWalker(const entry_ref *ref) + : fDirs(20), + fTopIndex(-1), + fTopDir(0), + fIncludeTopDir(false), + fOriginalIncludeTopDir(false), + fJustFile(0), + fOriginalDirCopy(ref), + fOriginalJustFile(0) +{ + if (fOriginalDirCopy.InitCheck() != B_OK) { + // not a directory, set up walking a single file + fJustFile = new BEntry(ref); + if (fJustFile->InitCheck() != B_OK) { + delete fJustFile; + fJustFile = NULL; + } + fOriginalJustFile = fJustFile; + } else { + fTopDir = new BDirectory(fOriginalDirCopy); + fTopIndex++; + fDirs.AddItem(fTopDir); + } +} + +TNodeWalker::TNodeWalker(const BDirectory *dir) + : fDirs(20), + fTopIndex(-1), + fTopDir(0), + fIncludeTopDir(false), + fOriginalIncludeTopDir(false), + fJustFile(0), + fOriginalDirCopy(*dir), + fOriginalJustFile(0) +{ + fTopDir = new BDirectory(*dir); + fTopIndex++; + fDirs.AddItem(fTopDir); +} + +TNodeWalker::~TNodeWalker() +{ + delete fOriginalJustFile; + + for (;;) { + BDirectory *directory = fDirs.RemoveItemAt(fTopIndex--); + if (directory == NULL) + break; + delete directory; + } +} + +status_t +TNodeWalker::PopDirCommon() +{ + ASSERT(fTopIndex >= 0); + + // done with the old dir, pop it + fDirs.RemoveItemAt(fTopIndex); + fTopIndex--; + delete fTopDir; + fTopDir = NULL; + + if (fTopIndex == -1) + // done + return B_ENTRY_NOT_FOUND; + + // point to the new top dir + fTopDir = fDirs.ItemAt(fTopIndex); + + return B_OK; +} + +void +TNodeWalker::PushDirCommon(const entry_ref *ref) +{ + fTopDir = new BDirectory(ref); + // OK to ignore error here. Will + // catch at next call to GetNextEntry + fTopIndex++; + fDirs.AddItem(fTopDir); +} + +status_t +TNodeWalker::GetNextEntry(BEntry *entry, bool traverse) +{ + if (fJustFile) { + *entry = *fJustFile; + fJustFile = 0; + return B_OK; + } + + if (!fTopDir) + // done + return B_ENTRY_NOT_FOUND; + + // If requested to include the top directory, return that first. + if (fIncludeTopDir) { + fIncludeTopDir = false; + return fTopDir->GetEntry(entry); + } + + // Get the next entry. + status_t err = fTopDir->GetNextEntry(entry, traverse); + + if (err != B_OK) { + err = PopDirCommon(); + if (err != B_OK) + return err; + return GetNextEntry(entry, traverse); + } + // See if this entry is a directory. If it is then push it onto the + // stack + entry_ref ref; + err = entry->GetRef(&ref); + + if (err == B_OK && fTopDir->Contains(ref.name, B_DIRECTORY_NODE)) + PushDirCommon(&ref); + + return err; +} + +status_t +TNodeWalker::GetNextRef(entry_ref *ref) +{ + if (fJustFile) { + fJustFile->GetRef(ref); + fJustFile = 0; + return B_OK; + } + + if (!fTopDir) + // done + return B_ENTRY_NOT_FOUND; + + // If requested to include the top directory, return that first. + if (fIncludeTopDir) { + fIncludeTopDir = false; + BEntry entry; + status_t err = fTopDir->GetEntry(&entry); + if (err == B_OK) + err = entry.GetRef(ref); + return err; + } + + // Get the next entry. + status_t err = fTopDir->GetNextRef(ref); + if (err != B_OK) { + err = PopDirCommon(); + if (err != B_OK) + return err; + return GetNextRef(ref); + } + // See if this entry is a directory. If it is then push it onto the + // stack + + if (fTopDir->Contains(ref->name, B_DIRECTORY_NODE)) + PushDirCommon(ref); + + return B_OK; +} + +static int32 +build_dirent(const BEntry *source, struct dirent *ent, + size_t size, int32 count) +{ + entry_ref ref; + source->GetRef(&ref); + + size_t recordLength = strlen(ref.name) + sizeof(dirent); + if (recordLength > size || count <= 0) + // can't fit in buffer, bail + return 0; + + // info about this node + ent->d_reclen = static_cast(recordLength); + strcpy(ent->d_name, ref.name); + ent->d_dev = ref.device; + ent->d_ino = ref.directory; + + // info about the parent + BEntry parent; + source->GetParent(&parent); + if (parent.InitCheck() == B_OK) { + entry_ref parentRef; + parent.GetRef(&parentRef); + ent->d_pdev = parentRef.device; + ent->d_pino = parentRef.directory; + } else { + ent->d_pdev = 0; + ent->d_pino = 0; + } + + return 1; +} + +int32 +TNodeWalker::GetNextDirents(struct dirent *ent, size_t size, int32 count) +{ + if (fJustFile) { + if (!count) + return 0; + + // simulate GetNextDirents by building a single dirent structure + int32 result = build_dirent(fJustFile, ent, size, count); + fJustFile = 0; + return result; + } + + if (!fTopDir) + // done + return 0; + + // If requested to include the top directory, return that first. + if (fIncludeTopDir) { + fIncludeTopDir = false; + BEntry entry; + if (fTopDir->GetEntry(&entry) < B_OK) + return 0; + + return build_dirent(fJustFile, ent, size, count); + } + + // Get the next entry. + int32 result = fTopDir->GetNextDirents(ent, size, count); + + if (!result) { + status_t err = PopDirCommon(); + if (err != B_OK) + return 0; + + return GetNextDirents(ent, size, count); + } + + // push any directories in the returned entries onto the stack + for (int32 i = 0; i < result; i++) { + if (fTopDir->Contains(ent->d_name, B_DIRECTORY_NODE)) { + entry_ref ref(ent->d_dev, ent->d_ino, ent->d_name); + PushDirCommon(&ref); + } + ent = (dirent *)((char *)ent + ent->d_reclen); + } + + return result; +} + +status_t +TNodeWalker::Rewind() +{ + if (fOriginalJustFile) { + // single file mode, rewind by pointing to the original file + fJustFile = fOriginalJustFile; + return B_OK; + } + + // pop all the directories and point to the initial one + for (;;) { + BDirectory *directory = fDirs.RemoveItemAt(fTopIndex--); + if (!directory) + break; + delete directory; + } + + fTopDir = new BDirectory(fOriginalDirCopy); + fTopIndex = 0; + fIncludeTopDir = fOriginalIncludeTopDir; + fDirs.AddItem(fTopDir); + // rewind the directory + return fTopDir->Rewind(); +} + +int32 +TNodeWalker::CountEntries() +{ + // should not be calling this + TRESPASS(); + return -1; +} + +TVolWalker::TVolWalker(bool knowsAttributes, bool writable, bool includeTopDirectory) + : TNodeWalker(includeTopDirectory), + fKnowsAttr(knowsAttributes), + fWritable(writable) +{ + + /* + Get things initialized. Find first volume, or find the first volume + that supports attributes. + */ + NextVolume(); +} + +TVolWalker::~TVolWalker() +{ +} + +status_t +TVolWalker::NextVolume() +{ + status_t err; + + // The stack of directoies should be empty. + ASSERT(fTopIndex == -1); + ASSERT(fTopDir == NULL); + + do { + err = fVolRoster.GetNextVolume(&fVol); + if (err != B_OK) + break; + } while ((fKnowsAttr && !fVol.KnowsAttr()) || (fWritable && fVol.IsReadOnly())); + + if (err == B_OK) { + // Get the root directory to get things started. There's always + // a root directory for a volume. So if there is an error then it + // means that something is really bad, like the system is out of + // memory. In that case don't worry about truying to skip to the + // next volume. + fTopDir = new BDirectory(); + err = fVol.GetRootDirectory(fTopDir); + fIncludeTopDir = fOriginalIncludeTopDir; + fTopIndex = 0; + fDirs.AddItem(fTopDir); + } + + return err; +} + +status_t +TVolWalker::GetNextEntry(BEntry *entry, bool traverse) +{ + if (!fTopDir) + return B_ENTRY_NOT_FOUND; + + // Get the next entry. + status_t err = _inherited::GetNextEntry(entry, traverse); + + while (err != B_OK) { + // We're done with the current volume. Go to the next one + err = NextVolume(); + if (err != B_OK) + break; + err = GetNextEntry(entry, traverse); + } + + return err; +} + +status_t +TVolWalker::GetNextRef(entry_ref *ref) +{ + if (!fTopDir) + return B_ENTRY_NOT_FOUND; + + // Get the next ref. + status_t err = _inherited::GetNextRef(ref); + + while (err != B_OK) { + // We're done with the current volume. Go to the next one + err = NextVolume(); + if (err != B_OK) + break; + err = GetNextRef(ref); + } + + return err; +} + +int32 +TVolWalker::GetNextDirents(struct dirent *ent, size_t size, int32 count) +{ + if (!fTopDir) + return B_ENTRY_NOT_FOUND; + + // Get the next dirent. + status_t err = _inherited::GetNextDirents(ent, size, count); + + while (err != B_OK) { + // We're done with the current volume. Go to the next one + err = NextVolume(); + if (err != B_OK) + break; + err = GetNextDirents(ent, size, count); + } + + return err; +} + +status_t +TVolWalker::Rewind() +{ + fVolRoster.Rewind(); + return NextVolume(); +} + +TQueryWalker::TQueryWalker(const char *predicate) + : TWalker(), fQuery(), fVolRoster(), fVol() +{ + fPredicate = strdup(predicate); + NextVolume(); +} + +TQueryWalker::~TQueryWalker() +{ + free((char*) fPredicate); + fPredicate = NULL; +} + +status_t +TQueryWalker::GetNextEntry(BEntry *entry, bool traverse) +{ + status_t err; + + do { + err = fQuery.GetNextEntry(entry, traverse); + if (err == B_ENTRY_NOT_FOUND) { + if (NextVolume() != B_OK) + break; + } + } while (err == B_ENTRY_NOT_FOUND); + + return err; +} + +status_t +TQueryWalker::GetNextRef(entry_ref *ref) +{ + status_t err; + + for (;;) { + err = fQuery.GetNextRef(ref); + if (err != B_ENTRY_NOT_FOUND) + break; + + err = NextVolume(); + if (err != B_OK) + break; + } + + return err; +} + +int32 +TQueryWalker::GetNextDirents(struct dirent *ent, size_t size, int32 count) +{ + int32 result; + + for (;;) { + result = fQuery.GetNextDirents(ent, size, count); + if (result != 0) + return result; + + if (NextVolume() != B_OK) + return 0; + } + + return result; +} + +status_t +TQueryWalker::NextVolume() +{ + status_t err; + do { + err = fVolRoster.GetNextVolume(&fVol); + if (err) + break; + } while (!fVol.KnowsQuery()); + + + if (err == B_OK) { + err = fQuery.Clear(); + err = fQuery.SetVolume(&fVol); + err = fQuery.SetPredicate(fPredicate); + err = fQuery.Fetch(); + } + + return err; +} + +int32 +TQueryWalker::CountEntries() +{ + // should not be calling this + TRESPASS(); + return -1; +} + +status_t +TQueryWalker::Rewind() +{ + fVolRoster.Rewind(); + return NextVolume(); +} + +} // namespace BTrackerPrivate diff --git a/src/kits/tracker/NodeWalker.h b/src/kits/tracker/NodeWalker.h new file mode 100644 index 0000000000..2770dfea92 --- /dev/null +++ b/src/kits/tracker/NodeWalker.h @@ -0,0 +1,189 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#if !OPEN_TRACKER + +#include + +#define WALKER_NS + +#else + +#ifndef WALKER_H +#define WALKER_H + +#ifndef _BE_BUILD_H +#include +#endif +#include +#include +#include +#include +#include +#include +#include + +#include "ObjectList.h" + +namespace BTrackerPrivate { +#define WALKER_NS BTrackerPrivate + + +class TWalker : public BEntryList { + // adds a virtual destructor that is severely missing in BEntryList + // BEntryList should never be used polymorphically because of that + +public: + virtual ~TWalker(); + + virtual status_t GetNextEntry(BEntry *, bool traverse = false) = 0; + virtual status_t GetNextRef(entry_ref *) = 0; + virtual int32 GetNextDirents(struct dirent *, size_t, + int32 count = INT_MAX) = 0; + virtual status_t Rewind() = 0; + virtual int32 CountEntries() = 0; +}; + +class TNodeWalker : public TWalker { +// TNodeWalker supports iterating a single volume, starting from a specified +// entry; if passed a non-directory entry it returns just that one entry +public: + TNodeWalker(bool includeTopDirectory); + TNodeWalker(const char *path, bool includeTopDirectory); + TNodeWalker(const entry_ref *ref, bool includeTopDirectory); + TNodeWalker(const BDirectory *dir, bool includeTopDirectory); + virtual ~TNodeWalker(); + + // Backwards compatibility with Tracker compiled for R5 (remove when this + // gets integrated into the official release). + TNodeWalker(); + TNodeWalker(const char *path); + TNodeWalker(const entry_ref *ref); + TNodeWalker(const BDirectory *dir); + + virtual status_t GetNextEntry(BEntry *, bool traverse = false); + virtual status_t GetNextRef(entry_ref *); + virtual int32 GetNextDirents(struct dirent *, size_t, + int32 count = INT_MAX); + virtual status_t Rewind(); + +protected: + status_t PopDirCommon(); + void PushDirCommon(const entry_ref *); + +private: + virtual int32 CountEntries(); + // don't know how to do that, have just a fake stub here + +protected: + BObjectList fDirs; + int32 fTopIndex; + BDirectory *fTopDir; + bool fIncludeTopDir; + bool fOriginalIncludeTopDir; + +private: + BEntry *fJustFile; + BDirectory fOriginalDirCopy; + BEntry *fOriginalJustFile; + // keep around to support Rewind +}; + +class TVolWalker : public TNodeWalker { +// TNodeWalker supports iterating over all the mounted volumes; +// non-attribute and read-only volumes may optionaly be filtered out +public: + TVolWalker(bool knows_attr = true, bool writable = true, + bool includeTopDirectory = true); + virtual ~TVolWalker(); + + virtual status_t GetNextEntry(BEntry *, bool traverse = false); + virtual status_t GetNextRef(entry_ref *); + virtual int32 GetNextDirents(struct dirent *, size_t, + int32 count = INT_MAX); + virtual status_t Rewind(); + + virtual status_t NextVolume(); + // skips to the next volume + // Note: it would be cool to return const BVolume * + // that way a subclass could implement a volume filter - + // it would just override, call inherited for as long as there + // are volumes and it does not like them + // we would have to give up the status_t then, which might be + // ok + +private: + BVolumeRoster fVolRoster; + BVolume fVol; + bool fKnowsAttr; + bool fWritable; + + typedef TNodeWalker _inherited; +}; + +class TQueryWalker : public TWalker { +public: + TQueryWalker(const char *predicate); + virtual ~TQueryWalker(); + + // Does an in-fix walk of all entries + virtual status_t GetNextEntry(BEntry *, bool traverse = false); + virtual status_t GetNextRef(entry_ref *); + virtual int32 GetNextDirents(struct dirent *, size_t, + int32 count = INT_MAX); + + virtual status_t NextVolume(); + // skips to the next volume + virtual status_t Rewind(); + +private: + virtual int32 CountEntries(); + // can't count + + BQuery fQuery; + BVolumeRoster fVolRoster; + BVolume fVol; + bigtime_t fTime; + const char *fPredicate; + + typedef TQueryWalker _inherited; +}; + +} // namespace BTrackerPrivate + +using namespace BTrackerPrivate; + +#endif // WALKER_H + +#endif // B_BEOS_VERSION_DANO diff --git a/src/kits/tracker/OpenHashTable.h b/src/kits/tracker/OpenHashTable.h new file mode 100644 index 0000000000..ba9a7bf15d --- /dev/null +++ b/src/kits/tracker/OpenHashTable.h @@ -0,0 +1,376 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// Hash table with open addresssing + +#ifndef __OPEN_HASH_TABLE__ +#define __OPEN_HASH_TABLE__ + +#include +#include + +namespace BPrivate { + +template +class ElementVector { + // element vector for OpenHashTable needs to implement this + // interface + public: + Element &At(int32 index); + Element *Add(); + int32 IndexOf(const Element &) const; + void Remove(int32 index); +}; + +class OpenHashElement { + public: + uint32 Hash() const; + bool operator==(const OpenHashElement &) const; + void Adopt(OpenHashElement &); + // low overhead copy, original element is in undefined state + // after call (calls Adopt on BString members, etc.) + int32 fNext; +}; + +const uint32 kPrimes [] = { + 509, 1021, 2039, 4093, 8191, 16381, 32749, 65521, 131071, 262139, + 524287, 1048573, 2097143, 4194301, 8388593, 16777213, 33554393, 67108859, + 134217689, 268435399, 536870909, 1073741789, 2147483647, 0 +}; + +template > +class OpenHashTable { + public: + OpenHashTable(int32 minSize, ElementVec *elementVector = 0); + // it is up to the subclass of OpenHashTable to supply + // elementVector + ~OpenHashTable(); + + void SetElementVector(ElementVec *elementVector); + + Element *FindFirst(uint32 elementHash) const; + Element &Add(uint32 elementHash); + + void Remove(Element *); + + // when calling Add, any outstanding element pointer may become + // invalid; to deal with this, get the element index and restore + // it after the add + int32 ElementIndex(const Element *) const; + Element *ElementAt(int32 index) const; + + int32 VectorSize() const; + + protected: + static int32 OptimalSize(int32 minSize); + + int32 fArraySize; + int32 *fHashArray; + ElementVec *fElementVector; +}; + + +template +class OpenHashElementArray : public ElementVector { + // this is a straightforward implementation of an element vector + // deleting is handled by linking deleted elements into a free list + // the vector never shrinks + public: + OpenHashElementArray(int32 initialSize); + ~OpenHashElementArray(); + + Element &At(int32 index); + const Element &At(int32 index) const; + int32 Add(const Element &); + int32 Add(); + void Remove(int32 index); + int32 IndexOf(const Element &) const; + int32 Size() const; + + private: + Element *fData; + int32 fSize; + int32 fNextFree; + int32 fNextDeleted; +}; + + +//--- inline implementation -------------------------------- + + +template +OpenHashTable::OpenHashTable(int32 minSize,ElementVec *elementVector) + : + fArraySize(OptimalSize(minSize)), + fElementVector(elementVector) +{ + fHashArray = new int32[fArraySize]; + for (int32 index = 0; index < fArraySize; index++) + fHashArray[index] = -1; +} + + +template +OpenHashTable::~OpenHashTable() +{ + delete fHashArray; +} + + +template +int32 +OpenHashTable::OptimalSize(int32 minSize) +{ + for (int32 index = 0; ; index++) + if (!kPrimes[index] || kPrimes[index] >= (uint32)minSize) + return (int32)kPrimes[index]; + + return 0; +} + + +template +Element * +OpenHashTable::FindFirst(uint32 hash) const +{ + ASSERT(fElementVector); + hash %= fArraySize; + if (fHashArray[hash] < 0) + return 0; + + return &fElementVector->At(fHashArray[hash]); +} + + +template +int32 +OpenHashTable::ElementIndex(const Element *element) const +{ + return fElementVector->IndexOf(*element); +} + + +template +Element * +OpenHashTable::ElementAt(int32 index) const +{ + return &fElementVector->At(index); +} + + +template +int32 +OpenHashTable::VectorSize() const +{ + return fElementVector->Size(); +} + + +template +Element & +OpenHashTable::Add(uint32 hash) +{ + ASSERT(fElementVector); + hash %= fArraySize; + Element &result = *fElementVector->Add(); + result.fNext = fHashArray[hash]; + fHashArray[hash] = fElementVector->IndexOf(result); + return result; +} + + +template +void +OpenHashTable::Remove(Element *element) +{ + uint32 hash = element->Hash() % fArraySize; + int32 next = fHashArray[hash]; + ASSERT(next >= 0); + + if (&fElementVector->At(next) == element) { + fHashArray[hash] = element->fNext; + fElementVector->Remove(next); + return; + } + + for (int32 index = next; index >= 0; ) { + // look for an existing match in table + int32 next = fElementVector->At(index).fNext; + if (next < 0) { + TRESPASS(); + return; + } + + if (&fElementVector->At(next) == element) { + fElementVector->At(index).fNext = element->fNext; + fElementVector->Remove(next); + return; + } + index = next; + } +} + + +template +void +OpenHashTable::SetElementVector(ElementVec *elementVector) +{ + fElementVector = elementVector; +} + + +template +OpenHashElementArray::OpenHashElementArray(int32 initialSize) + : + fSize(initialSize), + fNextFree(0), + fNextDeleted(-1) +{ + fData = (Element *)calloc((size_t)initialSize , sizeof(Element)); + if (!fData) + throw bad_alloc(); +} + + +template +OpenHashElementArray::~OpenHashElementArray() +{ + free(fData); +} + + +template +Element & +OpenHashElementArray::At(int32 index) +{ + ASSERT(index < fSize); + return fData[index]; +} + + +template +const Element & +OpenHashElementArray::At(int32 index) const +{ + ASSERT(index < fSize); + return fData[index]; +} + + +template +int32 +OpenHashElementArray::IndexOf(const Element &element) const +{ + int32 result = &element - fData; + if (result < 0 || result > fSize) + return -1; + + return result; +} + + +template +int32 +OpenHashElementArray::Size() const +{ + return fSize; +} + + +template +int32 +OpenHashElementArray::Add(const Element &newElement) +{ + int32 index = Add(); + At(index).Adopt(newElement); + return index; +} + + +#if DEBUG +const int32 kGrowChunk = 10; +#else +const int32 kGrowChunk = 1024; +#endif + + +template +int32 +OpenHashElementArray::Add() +{ + int32 index = fNextFree; + if (fNextDeleted >= 0) { + index = fNextDeleted; + fNextDeleted = At(index).fNext; + } else if (fNextFree >= fSize - 1) { + int32 newSize = fSize + kGrowChunk; + Element *newData = (Element *)calloc((size_t)newSize , sizeof(Element)); + if (!newData) + throw bad_alloc(); + memcpy(newData, fData, fSize * sizeof(Element)); + free(fData); + fData = newData; + fSize = newSize; + index = fNextFree; + fNextFree++; + } else + fNextFree++; + + new (&At(index)) Element; + // call placement new to initialize the element properly + ASSERT(At(index).fNext == -1); + + return index; +} + + +template +void +OpenHashElementArray::Remove(int32 index) +{ + // delete by chaining empty elements in a single linked + // list, reusing the next field + ASSERT(index < fSize); + At(index).~Element(); + // call the destructor explicitly to destroy the element + // properly + At(index).fNext = fNextDeleted; + fNextDeleted = index; +} + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/OpenWithWindow.cpp b/src/kits/tracker/OpenWithWindow.cpp new file mode 100644 index 0000000000..09a98e46cb --- /dev/null +++ b/src/kits/tracker/OpenWithWindow.cpp @@ -0,0 +1,1655 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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" + +const char *kDefaultOpenWithTemplate = "OpenWithSettings"; + +// ToDo: +// filter out trash +// allow column configuring +// make SaveState/RestoreState save the current window setting for +// other windows + +const float kMaxMenuWidth = 150; + +const int32 kLargeButtonWidth = 130; +const int32 kSmallButtonWidth = 60; +const BPoint kSmallButtonRect(kSmallButtonWidth, 20); +const BPoint kLargeButtonRect(kLargeButtonWidth, 20); +const int32 kOpenAndMakeDefault = 'OpDf'; +const rgb_color kOpenWithDefaultColor = { 0xFF, 0xFF, 0xCC, 255}; + + +OpenWithContainerWindow::OpenWithContainerWindow(BMessage * + entriesToOpen, LockingList *windowList, + window_look look, window_feel feel, uint32 flags, uint32 workspace) + : BContainerWindow(windowList, 0, look, feel, flags, workspace), + fEntriesToOpen(entriesToOpen) +{ + AutoLock lock(this); + + BRect windowRect(85, 50, 510, 296); + MoveTo(windowRect.LeftTop()); + ResizeTo(windowRect.Width(), windowRect.Height()); + + // add a background view; use the standard BackgroundView here, the same + // as the file panel is using + BRect rect(Bounds()); + BackgroundView *backgroundView = new BackgroundView(rect); + AddChild(backgroundView); + + rect = Bounds(); + + rect.OffsetTo(10, 15); + rect.bottom -= 60; // make room for buttons + + rect.right -= B_V_SCROLL_BAR_WIDTH + 20; // make room for scrollbars and + // a margin + rect.bottom -= B_H_SCROLL_BAR_HEIGHT; + fPoseView = NewPoseView(0, rect, kListMode); + backgroundView->AddChild(fPoseView); + + fPoseView->SetFlags(fPoseView->Flags() | B_NAVIGABLE); + fPoseView->SetPoseEditing(false); + + // add buttons + rect = Bounds(); + BRect buttonRect(rect); + buttonRect.InsetBy(30, 10); + buttonRect.SetLeftTop(buttonRect.RightBottom() - kSmallButtonRect); + fLaunchButton = new BButton(buttonRect, "ok", "Open", + new BMessage(kDefaultButton), B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM); + backgroundView->AddChild(fLaunchButton); + fLaunchButton->MakeDefault(true); + + buttonRect.OffsetTo(buttonRect.left - kLargeButtonWidth - 10, + buttonRect.top); + buttonRect.SetRightBottom(buttonRect.LeftTop() + kLargeButtonRect); + fLaunchAndMakeDefaultButton = new BButton(buttonRect, "make default", + "Open and Make Preferred", new BMessage(kOpenAndMakeDefault), + B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM); + // wide button, have to resize to fit text + fLaunchAndMakeDefaultButton->ResizeToPreferred(); + fLaunchAndMakeDefaultButton->MoveBy( + buttonRect.right - fLaunchAndMakeDefaultButton->Frame().right , 0); + backgroundView->AddChild(fLaunchAndMakeDefaultButton); + fLaunchAndMakeDefaultButton->SetEnabled(false); + + buttonRect = fLaunchAndMakeDefaultButton->Frame(); + buttonRect.OffsetTo(buttonRect.left - kSmallButtonWidth - 10, + buttonRect.top); + buttonRect.SetRightBottom(buttonRect.LeftTop() + kSmallButtonRect); + + backgroundView->AddChild(new BButton(buttonRect, "cancel", + "Cancel", new BMessage(kCancelButton), + B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM)); + + // set the window title + if (CountRefs(fEntriesToOpen) == 1) { + // if opening just one file, use it in the title + entry_ref ref; + fEntriesToOpen->FindRef("refs", &ref); + BString buffer; + buffer << "Open " << ref.name << " With:"; + SetTitle(buffer.String()); + } else + // use generic title + SetTitle("Open Selection With:"); + + AddCommonFilter(new BMessageFilter(B_KEY_DOWN, &OpenWithContainerWindow::KeyDownFilter)); +} + + +OpenWithContainerWindow::~OpenWithContainerWindow() +{ + delete fEntriesToOpen; +} + + +BPoseView * +OpenWithContainerWindow::NewPoseView(Model *, BRect rect, uint32) +{ + return new OpenWithPoseView(rect); +} + + +OpenWithPoseView * +OpenWithContainerWindow::PoseView() const +{ + ASSERT(dynamic_cast(fPoseView)); + return static_cast(fPoseView); +} + + +const BMessage * +OpenWithContainerWindow::EntryList() const +{ + return fEntriesToOpen; +} + + +void +OpenWithContainerWindow::OpenWithSelection() +{ + int32 count = PoseView()->SelectionList()->CountItems(); + ASSERT(count == 1); + if (!count) + return; + + PoseView()->OpenSelection(PoseView()->SelectionList()->FirstItem(), 0); +} + + +static const BString * +FindOne(const BString *element, void *castToString) +{ + if (strcasecmp(element->String(), (const char *)castToString) == 0) + return element; + + return 0; +} + + +static const entry_ref * +AddOneUniqueDocumentType(const entry_ref *ref, void *castToList) +{ + BObjectList *list = (BObjectList *)castToList; + + BEntry entry(ref, true); + // traverse symlinks + + // get this documents type + 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->EachElement(FindOne, &type)) + // type already in list, bail + return 0; + + // add type to list + list->AddItem(new BString(type)); + return 0; +} + + +static const BString * +SetDefaultAppForOneType(const BString *element, void *castToEntryRef) +{ + const entry_ref *appRef = (const entry_ref *)castToEntryRef; + + // set entry as default handler for one mime string + BMimeType mime(element->String()); + if (!mime.IsInstalled()) + return 0; + + // first set it's app signature as the preferred type + 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; + + // set the app hint on the metamime for this signature + 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()->SelectionList()->CountItems(); + ASSERT(count == 1); + if (!count) + return; + + BPose *selectedAppPose = PoseView()->SelectionList()->FirstItem(); + ASSERT(selectedAppPose); + if (!selectedAppPose) + return; + + // collect all the types of all the opened documents into a list + BObjectList openedFileTypes(10, true); + EachEntryRef(EntryList(), AddOneUniqueDocumentType, &openedFileTypes, 100); + + // set the default application to be the selected pose for all the + // mime types in the list + openedFileTypes.EachElement(SetDefaultAppForOneType, + (void *)selectedAppPose->TargetModel()->EntryRef()); + + // done setting the default application, now launch the app with the + // documents + 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; + } + _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 modifier=0; + message->FindInt32("modifiers", &modifier); + if (!modifier && key == B_ESCAPE) { + filter->Looper()->PostMessage(kCancelButton); + return B_SKIP_MESSAGE; + } + + return B_DISPATCH_MESSAGE; +} + + +void +OpenWithContainerWindow::AddShortcuts() +{ + // add get info here +} + + +void +OpenWithContainerWindow::NewAttributeMenu(BMenu *menu) +{ + _inherited::NewAttributeMenu(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("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("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::RestoreState() +{ + BNode defaultingNode; + if (DefaultStateSourceNode(kDefaultOpenWithTemplate, &defaultingNode, false)) { + AttributeStreamFileNode streamNodeSource(&defaultingNode); + RestoreWindowState(&streamNodeSource); + fPoseView->Init(&streamNodeSource); + } else { + RestoreWindowState(NULL); + fPoseView->Init(NULL); + } +} + + +void +OpenWithContainerWindow::RestoreState(const BMessage &message) +{ + _inherited::RestoreState(message); +} + + +void +OpenWithContainerWindow::RestoreWindowState(AttributeStreamNode *node) +{ + SetSizeLimits(310, 10000, 160, 10000); + if (!node) + 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); +} + + +// #pragma mark - + + +OpenWithPoseView::OpenWithPoseView(BRect frame, uint32 resizeMask) + : BPoseView(0, frame, kListMode, resizeMask), + fHaveCommonPreferredApp(false), + fIterator(NULL) +{ + fSavePoseLocations = false; + fMultipleSelection = false; + fDragEnabled = false; +} + + +OpenWithContainerWindow * +OpenWithPoseView::ContainerWindow() const +{ + ASSERT(dynamic_cast(Window())); + return static_cast(Window()); +} + + +void +OpenWithPoseView::AttachedToWindow() +{ + _inherited::AttachedToWindow(); + SetViewColor(kOpenWithDefaultColor); + SetLowColor(kOpenWithDefaultColor); +} + + +bool +OpenWithPoseView::CanHandleDragSelection(const Model *, const BMessage *, bool) +{ + return false; +} + + +static void +AddSupportingAppForTypeToQuery(SearchForSignatureEntryList *queryIterator, + const char *signature) +{ + // get supporting apps for type + BMimeType mime(signature); + if (!mime.IsInstalled()) + return; + + BMessage message; + mime.GetSupportingApps(&message); + + for (int32 index =0; ; index++) { + const char *signature; + int32 length; + + if (message.FindData("applications", 'CSTR', index, (const void **)&signature, + &length) != B_OK) + break; + + // push each of the supporting apps signature uniquely + queryIterator->PushUniqueSignature(signature); + } +} + + +static const entry_ref * +AddOneRefSignatures(const entry_ref *ref, void *castToIterator) +{ + // ToDo: + // resolve cases where each entry has a different type and + // their supporting apps are disjoint sets + + SearchForSignatureEntryList *queryIterator = + (SearchForSignatureEntryList *)castToIterator; + + Model model(ref, true, true); + if (model.InitCheck() != B_OK) + return NULL; + + BString mimeType(model.MimeType()); + + if (!mimeType.Length() || mimeType.ICompare(B_FILE_MIMETYPE) == 0) + // if model is of unknown type, try mimeseting it first + model.Mimeset(true); + + bool preferredAppFromNode = false; + entry_ref preferredRef; + + // add preferred app for file, if any + if (model.PreferredAppSignature()[0]) { + queryIterator->PushUniqueSignature(model.PreferredAppSignature()); + + // got one, mark it as preferred for this node + if (be_roster->FindApp(model.PreferredAppSignature(), &preferredRef) == B_OK) { + preferredAppFromNode = true; + queryIterator->TrySettingPreferredAppForFile(&preferredRef); + } + } + + mimeType = model.MimeType(); + mimeType.ToLower(); + + if (mimeType.Length() && !mimeType.ICompare(B_FILE_MIMETYPE) == 0) + queryIterator->NonGenericFileFound(); + + // get supporting apps for type + AddSupportingAppForTypeToQuery(queryIterator, mimeType.String()); + + // find the preferred app for this type + 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); + + // push all the supporting apps from all the entries into the + // search for signature iterator + EachEntryRef(entryList, AddOneRefSignatures, fIterator, 100); + + // push superhandlers + AddSupportingAppForTypeToQuery(fIterator, B_FILE_MIMETYPE); + fHaveCommonPreferredApp = fIterator->GetPreferredApp(&fPreferredRef); + + if (fIterator->Rewind() != B_OK) { + delete fIterator; + fIterator = NULL; + HideBarberPole(); + return NULL; + } + return fIterator; +} + + +void +OpenWithPoseView::OpenSelection(BPose *pose, int32 *) +{ + OpenWithContainerWindow *window = ContainerWindow(); + + int32 count = fSelectionList->CountItems(); + if (!count) + return; + + if (!pose) + pose = fSelectionList->FirstItem(); + + ASSERT(pose); + + BEntry entry(pose->TargetModel()->EntryRef()); + if (entry.InitCheck() != B_OK) { + BString errorString; + errorString << "Could not find application \"" + << pose->TargetModel()->Name() << "\""; + + (new BAlert("", errorString.String(), "OK", 0, 0, B_WIDTH_AS_USUAL, + B_WARNING_ALERT))->Go(); + return; + } + + + if (OpenWithRelation(pose->TargetModel()) == kNoRelation) { + if (!fIterator->GenericFilesOnly()) { + + BString warning; + warning << "The application \"" << pose->TargetModel()->Name() + << "\" does not support the type of document you are " + "about to open. Are you sure you want to proceed? If 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."; + + if ((new BAlert("", warning.String(), "Cancel", "Open", 0, + B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go() == 0) + return; + } // else - once we have an extensible sniffer, tell users to ask + // publishers to fix up sniffers + } + + + BMessage message(*window->EntryList()); + // make a clone to send + message.RemoveName("launchUsingSelector"); + // make sure the old selector is not in the message + message.AddRef("handler", pose->TargetModel()->EntryRef()); + // add ref of the selected handler + + ASSERT(fSelectionHandler); + + if (fSelectionHandler) + fSelectionHandler->PostMessage(&message); + + window->PostMessage(B_QUIT_REQUESTED); +} + + +void +OpenWithPoseView::Pulse() +{ + // disable the Open and make default button if the default + // app matches the selected app + // + // disable the Open button if no apps selected + + OpenWithContainerWindow *window = ContainerWindow(); + + if (!fSelectionList->CountItems()) { + window->SetCanSetAppAsDefault(false); + window->SetCanOpen(false); + _inherited::Pulse(); + return; + } + + // if we selected a non-handling application, don't allow setting + // it as preferred + Model *firstSelected = fSelectionList->FirstItem()->TargetModel(); + if (OpenWithRelation(firstSelected) == kNoRelation) { + window->SetCanSetAppAsDefault(false); + window->SetCanOpen(true); + _inherited::Pulse(); + return; + } + + // make the open button enabled, because we have na app selected + window->SetCanOpen(true); + if (!fHaveCommonPreferredApp) { + window->SetCanSetAppAsDefault(true); + _inherited::Pulse(); + return; + } + + ASSERT(fSelectionList->CountItems() == 1); + + // enable the Open and make default if selected application different + // from preferred app ref + window->SetCanSetAppAsDefault((*fSelectionList->FirstItem()-> + TargetModel()->EntryRef()) != fPreferredRef); + + _inherited::Pulse(); +} + + +void +OpenWithPoseView::SetUpDefaultColumnsIfNeeded() +{ + // in case there were errors getting some columns + if (fColumnList->CountItems() != 0) + return; + + BColumn *nameColumn = new BColumn("Name", kColumnStart, 125, B_ALIGN_LEFT, + kAttrStatName, B_STRING_TYPE, true, true); + fColumnList->AddItem(nameColumn); + BColumn *relationColumn = new BColumn("Relation", 180, 100, B_ALIGN_LEFT, + kAttrOpenWithRelation, B_STRING_TYPE, false, false); + fColumnList->AddItem(relationColumn); + fColumnList->AddItem(new BColumn("Path", 290, 225, B_ALIGN_LEFT, + kAttrPath, B_STRING_TYPE, true, false)); + fColumnList->AddItem(new BColumn("Version", 525, 70, B_ALIGN_LEFT, + kAttrAppVersion, B_STRING_TYPE, false, false)); + + // sort by relation and by name + 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) +{ + // overridden to try to select the preferred handling app + _inherited::CreatePoses(models, poseInfoArray, count, resultingPoses, insertionSort, + lastPoseIndexPtr, boundsPtr, forceDraw); + + if (resultingPoses) + for (int32 index = 0; index < count; index++) + if (resultingPoses[index] && fHaveCommonPreferredApp + && *(models[index]->EntryRef()) == fPreferredRef) + // this is our preferred app, select it's pose + SelectPose(resultingPoses[index], IndexOfPose(resultingPoses[index])); +} + + +void +OpenWithPoseView::KeyDown(const char *bytes, int32 count) +{ + if (bytes[0] == B_TAB) + // just shift the focus, don't tab to the next pose + 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 *) +{ + // do nothing +} + + +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 + // in debug mode allow tweaking the colors + const rgb_color *color; + int32 size; + // handle roColour-style color drops + 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); +} + + +bool +OpenWithPoseView::ShouldShowPose(const Model *model, const PoseInfo *poseInfo) +{ + OpenWithContainerWindow *window = ContainerWindow(); + // filter for add_poses + if (!fIterator->CanOpenWithFilter(model, window->EntryList(), + fHaveCommonPreferredApp ? &fPreferredRef : 0)) + return false; + + return _inherited::ShouldShowPose(model, poseInfo); +} + + +// #pragma mark - + + +RelationCachingModelProxy::RelationCachingModelProxy(Model *model) + : fModel(model), + relation(kUnknownRelation) +{ +} + + +RelationCachingModelProxy::~RelationCachingModelProxy() +{ + delete fModel; +} + +int32 +RelationCachingModelProxy::Relation(SearchForSignatureEntryList *iterator, + BMessage *entries) const +{ + if (relation == kUnknownRelation) + relation = iterator->Relation(entries, fModel); + + return relation; +} + + +// #pragma mark - + + +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(); + + SetFont(be_plain_font); + + // too long to have triggers + 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(); + + SetFont(be_plain_font); + + // too long to have triggers + SetTriggersEnabled(false); +} + + +namespace BPrivate { + +int +SortByRelationAndName(const RelationCachingModelProxy *model1, + const RelationCachingModelProxy *model2, void *castToMenu) +{ + OpenWithMenu *menu = (OpenWithMenu *)castToMenu; + + // find out the relations of app models to the opened entries + int32 relation1 = model1->Relation(menu->fIterator, &menu->fEntriesToOpen); + int32 relation2 = model2->Relation(menu->fIterator, &menu->fEntriesToOpen); + + if (relation1 < relation2) { + // relation with the lowest number goes first + return 1; + } else if (relation1 > relation2) + return -1; + + // if relations match, sort by app name + return strcmp(model1->fModel->Name(), model2->fModel->Name()); +} + +} // namespace BPrivate + + +bool +OpenWithMenu::StartBuildingItemList() +{ + fIterator = new SearchForSignatureEntryList(false); + // push all the supporting apps from all the entries into the + // search for signature iterator + EachEntryRef(&fEntriesToOpen, AddOneRefSignatures, fIterator, 100); + // add superhandlers + 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(20, true); + + //queryRetrieval = new BStopWatch("get next entry on BQuery"); + 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)) { + // only allow executables, filter out multiple copies of the + // Tracker, filter out version that don't list the correct types, + // etc. + delete model; + } else + fSupportingAppList->AddItem(new RelationCachingModelProxy(model)); + + return true; +} + + +void +OpenWithMenu::DoneBuildingItemList() +{ + // sort by app name + fSupportingAppList->SortItems(SortByRelationAndName, this); + + // check if each app is unique + bool unique = true; + int32 count = fSupportingAppList->CountItems(); + for (int32 index = 0; index < count - 1; index++) { + // the list is sorted, just compare two adjacent models + if (strcmp(fSupportingAppList->ItemAt(index)->fModel->Name(), + fSupportingAppList->ItemAt(index + 1)->fModel->Name()) == 0) { + unique = false; + break; + } + } + + // add apps as menu items + BFont font; + GetFont(&font); + + int32 lastRelation = -1; + for (int32 index = 0; index < count ; index++) { + RelationCachingModelProxy *modelProxy = fSupportingAppList->ItemAt(index); + Model *model = modelProxy->fModel; + BMessage *message = new BMessage(fEntriesToOpen); + message->AddRef("handler", model->EntryRef()); + BContainerWindow *window = dynamic_cast(fParentWindow); + if (window) + message->AddData("nodeRefsToClose", B_RAW_TYPE, window->TargetModel()->NodeRef(), + sizeof (node_ref)); + + BString result; + if (unique) { + // just use the app name + result = model->Name(); + } else { + // get a truncated full path + BPath path; + BEntry entry(model->EntryRef()); + if (entry.GetPath(&path) != B_OK) { + PRINT(("stale entry ref %s\n", model->Name())); + delete message; + continue; + } + result = path.Path(); + font.TruncateString(&result, B_TRUNCATE_MIDDLE, kMaxMenuWidth); + } +#if DEBUG + BString relationDescription; + fIterator->RelationDescription(&fEntriesToOpen, model, &relationDescription); + result += " ("; + result += relationDescription; + result += ")"; +#endif + + // divide different relations of opening with a separator + int32 relation = modelProxy->Relation(fIterator, &fEntriesToOpen); + if (lastRelation != -1 && relation != lastRelation) + AddSeparatorItem(); + lastRelation = relation; + + ModelMenuItem *item = new ModelMenuItem(model, result.String(), message); + AddItem(item); + // mark item if it represents the preferred app + if (fHaveCommonPreferredApp && *(model->EntryRef()) == fPreferredRef) { + //PRINT(("marking item for % as preferred", model->Name())); + item->SetMarked(true); + } + } + + // target the menu + if (target) + SetTargetForItems(target); + else + SetTargetForItems(fMessenger); + + if (!CountItems()) { + BMenuItem *item = new BMenuItem("no supporting apps", 0); + item->SetEnabled(false); + AddItem(item); + } +} + + +void +OpenWithMenu::ClearMenuBuildingState() +{ + delete fIterator; + fIterator = NULL; + delete fSupportingAppList; + fSupportingAppList = NULL; +} + + +// #pragma mark - + + +SearchForSignatureEntryList::SearchForSignatureEntryList(bool canAddAllApps) + : fIteratorList(NULL), + fSignatures(20, true), + fPreferredAppCount(0), + fPreferredAppForFileCount(0), + fGenericFilesOnly(true), + fCanAddAllApps(canAddAllApps), + fFoundOneNonSuperHandler(false) +{ +} + + +SearchForSignatureEntryList::~SearchForSignatureEntryList() +{ + delete fIteratorList; +} + + +void +SearchForSignatureEntryList::PushUniqueSignature(const char *str) +{ + // do a unique add + if (fSignatures.EachElement(FindOne, (void *)str)) + return; + + fSignatures.AddItem(new BString(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 const BString * +AddOnePredicateTerm(const BString *item, void *castToParams) +{ + AddOneTermParams *params = (AddOneTermParams *)castToParams; + if (!params->first) + (*params->result) << " || "; + (*params->result) << kAttrAppSignature << " = " << item->String(); + + params->first = false; + + return 0; +} + + +status_t +SearchForSignatureEntryList::Rewind() +{ + if (fIteratorList) + return fIteratorList->Rewind(); + + if (!fSignatures.CountItems()) + return ENOENT; + + // build up the iterator + fIteratorList = new CachedEntryIteratorList; + + // build the predicate string by oring queries for the individual + // signatures + BString predicateString; + + AddOneTermParams params; + params.result = &predicateString; + params.first = true; + + fSignatures.EachElement(AddOnePredicateTerm, ¶ms); + + ASSERT(predicateString.Length()); +// PRINT(("query predicate %s\n", predicateString.String())); + fIteratorList->AddItem(new TWalkerWrapper( + new WALKER_NS::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) + // if more than one, will not return any + fPreferredAppCount++; +} + + +void +SearchForSignatureEntryList::TrySettingPreferredAppForFile(const entry_ref *ref) +{ + if (!fPreferredAppForFileCount) { + fPreferredRefForFile = *ref; + fPreferredAppForFileCount++; + } else if (fPreferredRefForFile != *ref) { + // if more than one, will not return any + 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) +{ + switch (applicationModel->SupportsMimeType(nodeModel->MimeType(), 0, true)) { + 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; + + // need to init a model so that typeless folders etc. will still appear to + // have a mime type + + Model model(&ref, true, true); + if (model.InitCheck()) + continue; + + int32 result = Relation(&model, applicationModel); + if (result != kNoRelation) { + if (preferredAppForFile + && *applicationModel->EntryRef() == *preferredAppForFile) + return kPreferredForFile; + + if (result == kSupportsType && preferredApp + && *applicationModel->EntryRef() == *preferredApp) + // application matches cached preferred app, we are done + 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 = "Preferred for file"; + return; + } + + Model model(&ref, true, true); + if (model.InitCheck()) + continue; + + BMimeType mimeType; + int32 result = Relation(&model, applicationModel); + switch (result) { + case kDoesNotSupportType: + continue; + + case kSuperhandler: + *description = "Handles any file"; + return; + + case kSupportsSupertype: + { + mimeType.SetTo(model.MimeType()); + // status_t result = mimeType.GetSupertype(&mimeType); + + char *type = (char *)mimeType.Type(); + char *tmp = strchr(type, '/'); + if (tmp) + *tmp = '\0'; + + //PRINT(("getting supertype for %s, result %s, got %s\n", + // model.MimeType(), strerror(result), mimeType.Type())); + *description = "Handles any "; + // *description += mimeType.Type(); + *description += type; + return; + } + + case kSupportsType: + { + mimeType.SetTo(model.MimeType()); + + if (preferredApp && *applicationModel->EntryRef() == *preferredApp) + // application matches cached preferred app, we are done + *description = "Preferred for "; + else + *description = "Handles "; + + char shortDescription[256]; + if (mimeType.GetShortDescription(shortDescription) == B_OK) + *description += shortDescription; + else + *description += mimeType.Type(); + return; + } + } + } + + *description = "Does not handle file"; +} + + +bool +SearchForSignatureEntryList::CanOpenWithFilter(const Model *appModel, + const BMessage *entriesToOpen, const entry_ref *preferredApp) +{ + if (!appModel->IsExecutable() || !appModel->Node()) { + // weed out non-executable +#if xDEBUG + BPath path; + BEntry entry(appModel->EntryRef()); + entry.GetPath(&path); + PRINT(("filtering out %s- not executable \n", path.Path())); +#endif + return false; + } + + if (strcmp(appModel->MimeType(), B_APP_MIME_TYPE) != 0) + // filter out pe containers on PPC etc. + return false; + + ASSERT(dynamic_cast(appModel->Node())); + char signature[B_MIME_TYPE_LENGTH]; + status_t result = GetAppSignatureFromAttr( + dynamic_cast(appModel->Node()), signature); + + + if (result == B_OK && strcasecmp(signature, kTrackerSignature) == 0) { + // special case the Tracker - make sure only the running copy is + // in the list + app_info trackerInfo; + result = be_roster->GetActiveAppInfo(&trackerInfo); + if (*appModel->EntryRef() != trackerInfo.ref) { + // this is an inactive copy of the Tracker, remove it + +#if xDEBUG + BPath path, path2; + BEntry entry(appModel->EntryRef()); + entry.GetPath(&path); + + BEntry entry2(&trackerInfo.ref); + entry2.GetPath(&path2); + + PRINT(("filtering out %s, sig %s, active Tracker at %s, result %s, refName %s\n", + path.Path(), signature, path2.Path(), strerror(result), + trackerInfo.ref.name)); +#endif + return false; + } + } + + if (FSInTrashDir(appModel->EntryRef())) + return false; + + if (ShowAllApplications()) { + // don't check for these if we didn't look for every single app + // to not slow filtering down + uint32 flags; + BAppFileInfo appFileInfo(dynamic_cast(appModel->Node())); + if (appFileInfo.GetAppFlags(&flags) != B_OK) + return false; + + if ((flags & B_BACKGROUND_APP) || (flags & B_ARGV_ONLY)) + return false; + + if (!signature[0]) + // weed out apps with empty signatures + 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) + // we hit at least one app that is not a superhandler and + // handles the document + fFoundOneNonSuperHandler = true; + + return true; +} + + +// #pragma mark - + + +ConditionalAllAppsIterator::ConditionalAllAppsIterator( + SearchForSignatureEntryList *parent) + : fParent(parent), + fWalker(NULL) +{ +} + + +void +ConditionalAllAppsIterator::Instantiate() +{ + if (fWalker) + return; + + BString lookForAppsPredicate; + lookForAppsPredicate << "(" << kAttrAppSignature << " = \"*\" ) && ( " + << kAttrMIMEType << " = " << B_APP_MIME_TYPE << " ) "; + fWalker = new WALKER_NS::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(); +} + diff --git a/src/kits/tracker/OpenWithWindow.h b/src/kits/tracker/OpenWithWindow.h new file mode 100644 index 0000000000..8753b1a401 --- /dev/null +++ b/src/kits/tracker/OpenWithWindow.h @@ -0,0 +1,325 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef _OPEN_WITH_WINDOW_H +#define _OPEN_WITH_WINDOW_H + +#include + +#include "ContainerWindow.h" +#include "EntryIterator.h" +#include "NodeWalker.h" +#include "PoseView.h" +#include "Query.h" +#include "SlowMenu.h" +#include "Utilities.h" + +namespace BPrivate { + +class OpenWithPoseView; + +// OpenWithContainerWindow supports the Open With feature + +enum { + kUnknownRelation = -1, + kNoRelation = 0, + kSuperhandler, + kSupportsSupertype, + kSupportsType, + kPreferredForType, + kPreferredForFile +}; + +class SearchForSignatureEntryList : public EntryListBase { + // pass in a predicate; a query will search for matches + // matches will be returned in iteration +public: + SearchForSignatureEntryList(bool canAddAllApps); + virtual ~SearchForSignatureEntryList(); + + void PushUniqueSignature(const char *); + // add one signature to search for + + // entry list iterators + virtual status_t GetNextEntry(BEntry *entry, bool traverse = false); + virtual status_t GetNextRef(entry_ref *ref); + virtual int32 GetNextDirents(struct dirent *buffer, size_t length, + int32 count = INT_MAX); + + virtual status_t Rewind(); + virtual int32 CountEntries(); + + bool GetPreferredApp(entry_ref *ref) const; + // gets the preferred app for all the files it was asked to + // find supporting apps for, returns false if no preferred app + // found or if more than one found + void TrySettingPreferredApp(const entry_ref *); + void TrySettingPreferredAppForFile(const entry_ref *); + + int32 Relation(const BMessage *entriesToOpen, const Model *) const; + // returns the reason why an application is shown in Open With window + void RelationDescription(const BMessage *entriesToOpen, const Model *, + BString *) const; + // returns a string describing why application handles files to open + + static int32 Relation(const BMessage *entriesToOpen, + const Model *, const entry_ref *preferredApp, + const entry_ref *preferredAppForFile); + // returns the reason why an application is shown in Open With window + // static version, needs the preferred app for preformance + static void RelationDescription(const BMessage *entriesToOpen, + const Model *, BString *, const entry_ref *preferredApp, + const entry_ref *preferredAppForFile); + // returns a string describing why application handles files to open + + bool CanOpenWithFilter(const Model *appModel, const BMessage *entriesToOpen, + const entry_ref *preferredApp); + + void NonGenericFileFound(); + bool GenericFilesOnly() const; + + bool ShowAllApplications() const; + +private: + static int32 Relation(const Model *node, const Model *app); + // returns the reason why an application is shown in Open With window + + CachedEntryIteratorList *fIteratorList; + BObjectList fSignatures; + + entry_ref fPreferredRef; + int32 fPreferredAppCount; + entry_ref fPreferredRefForFile; + int32 fPreferredAppForFileCount; + bool fGenericFilesOnly; + bool fCanAddAllApps; + bool fFoundOneNonSuperHandler; +}; + +class OpenWithContainerWindow : public BContainerWindow { +public: + OpenWithContainerWindow(BMessage *entriesToOpen, + LockingList *windowList, + window_look look = B_DOCUMENT_WINDOW_LOOK, + window_feel feel = B_NORMAL_WINDOW_FEEL, + uint32 flags = 0, + uint32 workspace = B_CURRENT_WORKSPACE); + // eventually get opened by the selected app + + ~OpenWithContainerWindow(); + + virtual void Init(const BMessage *message); + + const BMessage *EntryList() const; + // return the list of the entries we are supposed to open + + void SetCanSetAppAsDefault(bool); + void SetCanOpen(bool); + + OpenWithPoseView *PoseView() const; + +protected: + virtual BPoseView *NewPoseView(Model *model, BRect rect, uint32 viewMode); + + virtual bool ShouldAddMenus() const + { return false; } + virtual void ShowContextMenu(BPoint, const entry_ref *, BView *) + { } + virtual void AddShortcuts(); + virtual void NewAttributeMenu(BMenu *); + + virtual void RestoreState(); + virtual void RestoreState(const BMessage &); + virtual void RestoreWindowState(AttributeStreamNode *); + virtual void RestoreWindowState(const BMessage &); + virtual bool NeedsDefaultStateSetup(); + virtual void SaveState(bool hide = true); + virtual void SaveState(BMessage &) const; + virtual void SetUpDefaultState(); + + virtual bool IsShowing(const node_ref *) const; + virtual bool IsShowing(const entry_ref *) const; + + virtual void MessageReceived(BMessage *); + + void OpenWithSelection(); + // open entries with the selected app + void MakeDefaultAndOpen(); + // open entries with the selected app and make it the default handler + +private: + static filter_result KeyDownFilter(BMessage *, BHandler **, BMessageFilter *); + + BMessage *fEntriesToOpen; + BButton *fLaunchButton; + BButton *fLaunchAndMakeDefaultButton; + + typedef BContainerWindow _inherited; +}; + +class OpenWithPoseView : public BPoseView { +public: + OpenWithPoseView(BRect, uint32 resizeMask = B_FOLLOW_ALL); + + virtual void OpenSelection(BPose *, int32 *); + // open entries with the selected app + + int32 OpenWithRelation(const Model *) const; + // returns the reason why an application is shown in Open With window + void OpenWithRelationDescription(const Model *, BString *) const; + // returns a string describing why application handles files to open + + OpenWithContainerWindow *ContainerWindow() const; + + virtual bool AddPosesThreadValid(const entry_ref *) const; + +protected: + // don't do any volume watching and memtamime watching in open with panels for now + virtual void InitialStartWatching() {} + virtual void FinalStopWatching() {} + + virtual void AttachedToWindow(); + EntryListBase *InitDirentIterator(const entry_ref *ref); + + virtual void SetUpDefaultColumnsIfNeeded(); + // show launch window specific columns + + // empty overrides for functions that depend on having an fModel + virtual void SaveState(AttributeStreamNode *); + virtual void RestoreState(AttributeStreamNode *); + virtual void SaveState(BMessage &) const; + virtual void RestoreState(const BMessage &); + virtual void SavePoseLocations(BRect * = NULL); + virtual void MoveSelectionToTrash(bool selectNext = true); + virtual void MoveSelectionTo(BPoint, BPoint, BContainerWindow*); + virtual void MoveSelectionInto(Model* destFolder, BContainerWindow *srcWindow, + bool forceCopy, bool create_link = false); + virtual bool HandleMessageDropped(BMessage *); + virtual bool CanHandleDragSelection(const Model *, const BMessage *, bool); + + virtual bool Represents(const node_ref *) const; + virtual bool Represents(const entry_ref *) const; + + virtual void CreatePoses(Model **models, PoseInfo *poseInfoArray, int32 count, + BPose **resultingPoses, bool insertionSort = true, int32 *lastPoseIndexPtr = NULL, + BRect *boundsPtr = NULL, bool forceDraw = false); + // override to add selecting the default handling app for selection + + virtual bool ShouldShowPose(const Model *, const PoseInfo *); + + virtual void Pulse(); + + virtual void KeyDown(const char *bytes, int32 count); + +private: + entry_ref fPreferredRef; + bool fHaveCommonPreferredApp; + + SearchForSignatureEntryList *fIterator; + // private copy of the iterator pointer + + typedef BPoseView _inherited; +}; + +class RelationCachingModelProxy { +public: + RelationCachingModelProxy(Model *); + ~RelationCachingModelProxy(); + + int32 Relation(SearchForSignatureEntryList *, BMessage *entries) const; + + Model *fModel; + mutable int32 relation; +}; + +class OpenWithMenu : public BSlowMenu { +public: + OpenWithMenu(const char *, const BMessage *entriesToOpen, + BWindow *parentWindow, BHandler *); + OpenWithMenu(const char *, const BMessage *entriesToOpen, + BWindow *parentWindow, const BMessenger &); + +private: + virtual bool StartBuildingItemList(); + virtual bool AddNextItem(); + virtual void DoneBuildingItemList(); + virtual void ClearMenuBuildingState(); + + BMessage fEntriesToOpen; + BHandler *target; + BMessenger fMessenger; + + // menu building state + SearchForSignatureEntryList *fIterator; + entry_ref fPreferredRef; + BObjectList *fSupportingAppList; + bool fHaveCommonPreferredApp; + BWindow *fParentWindow; + + typedef BSlowMenu _inherited; + +friend int SortByRelationAndName(const RelationCachingModelProxy *, + const RelationCachingModelProxy *, void *); +}; + +class ConditionalAllAppsIterator : public EntryListBase { + // used for optionally showing the list of all apps. Do nothing + // until asked to iterate and only if supposed to do so +public: + ConditionalAllAppsIterator(SearchForSignatureEntryList *parent); + ~ConditionalAllAppsIterator(); + + virtual status_t GetNextEntry(BEntry *entry, bool traverse = false); + virtual status_t GetNextRef(entry_ref *ref); + virtual int32 GetNextDirents(struct dirent *buffer, size_t length, + int32 count = INT_MAX); + + virtual status_t Rewind(); + virtual int32 CountEntries(); + +protected: + bool Iterate() const; + void Instantiate(); + +private: + SearchForSignatureEntryList *fParent; + WALKER_NS::TWalker *fWalker; +}; + + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/OverrideAlert.cpp b/src/kits/tracker/OverrideAlert.cpp new file mode 100644 index 0000000000..0e2de3d19f --- /dev/null +++ b/src/kits/tracker/OverrideAlert.cpp @@ -0,0 +1,148 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// defines the status area drawn in the bottom left corner of a Tracker window + +#include +#include + +#include "OverrideAlert.h" + +OverrideAlert::OverrideAlert(const char *title, const char *text, + const char *button1, uint32 modifiers1, + const char *button2, uint32 modifiers2, + const char *button3, uint32 modifiers3, + button_width width, alert_type type) + : BAlert(title, text, button1, button2, button3, width, type), + fCurModifiers(0) +{ + fButtonModifiers[0] = modifiers1; + fButtonModifiers[1] = modifiers2; + fButtonModifiers[2] = modifiers3; + UpdateButtons(modifiers(), true); + + BPoint where = OverPosition(Frame().Width(), Frame().Height()); + MoveTo(where.x, where.y); +} + +OverrideAlert::OverrideAlert(const char *title, const char *text, + const char *button1, uint32 modifiers1, + const char *button2, uint32 modifiers2, + const char *button3, uint32 modifiers3, + button_width width, button_spacing spacing, alert_type type) + : BAlert(title, text, button1, button2, button3, width, spacing, type), + fCurModifiers(0) +{ + fButtonModifiers[0] = modifiers1; + fButtonModifiers[1] = modifiers2; + fButtonModifiers[2] = modifiers3; + UpdateButtons(modifiers(), true); + + BPoint where = OverPosition(Frame().Width(), Frame().Height()); + MoveTo(where.x, where.y); +} + +OverrideAlert::~OverrideAlert() +{ +} + +void +OverrideAlert::DispatchMessage(BMessage *message, BHandler *handler) +{ + if (message->what == B_KEY_DOWN || message->what == B_KEY_UP + || message->what == B_UNMAPPED_KEY_DOWN + || message->what == B_UNMAPPED_KEY_UP) { + uint32 modifiers; + if (message->FindInt32("modifiers", (int32 *)&modifiers) == B_OK) + UpdateButtons(modifiers); + } + BAlert::DispatchMessage(message, handler); +} + +BPoint +OverrideAlert::OverPosition(float width, float height) +{ + // This positions the alert window like a normal alert, put + // places it on top of the calling window if possible. + + BWindow *window = dynamic_cast(BLooper::LooperForThread(find_thread(NULL))); + BRect screenFrame; + BRect desirableRect; + screenFrame = BScreen(window).Frame(); + + if (window) { + // If we found a window associated with this calling thread, + // place alert over that window so that the first button is + // on top of it. This allows name editing confirmations to + // work with focus follows mouse -- when the alert goes away, + // the underlying window will still have focus. + + desirableRect = window->Frame(); + float midX = (desirableRect.left + desirableRect.right) / 2.0f; + float midY = (desirableRect.top * 3.0f + desirableRect.bottom) / 4.0f; + + desirableRect.left = midX - ceilf(width / 2.0f); + desirableRect.right = desirableRect.left+width; + desirableRect.top = midY - ceilf(height / 3.0f); + desirableRect.bottom = desirableRect.top + height; + + } else { + // Otherwise, just place alert in center of screen. + + desirableRect = screenFrame; + float midX = (desirableRect.left + desirableRect.right) / 2.0f; + float midY = (desirableRect.top * 3.0f + desirableRect.bottom) / 4.0f; + + desirableRect.left = midX - ceilf(width / 2.0f); + desirableRect.right = desirableRect.left + width; + desirableRect.top = midY - ceilf(height / 3.0f); + desirableRect.bottom = desirableRect.top + height; + } + + return desirableRect.LeftTop(); +} + +void +OverrideAlert::UpdateButtons(uint32 modifiers, bool force) +{ + if (modifiers == fCurModifiers && !force) + return; + + fCurModifiers = modifiers; + for (int32 i = 0; i < 3; i++) { + BButton *button = ButtonAt(i); + if (button) + button->SetEnabled(((fButtonModifiers[i] & fCurModifiers) == fButtonModifiers[i])); + } +} diff --git a/src/kits/tracker/OverrideAlert.h b/src/kits/tracker/OverrideAlert.h new file mode 100644 index 0000000000..5109d5474b --- /dev/null +++ b/src/kits/tracker/OverrideAlert.h @@ -0,0 +1,83 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef _OVERRIDE_ALERT_H +#define _OVERRIDE_ALERT_H + +// This is a special BAlert for which you can specify modifier +// keys that must be down for various buttons to be enabled. It +// is used when confirming changes in the BeOS directory to force +// the user to hold shift when confirming. + +// The alert also positions itself slightly differently than a +// normal BAlert, attempting to be on top of the calling window. +// This allows it to work when confirming rename operations with +// Focus Follows Mouse turned on. + +#include + +namespace BPrivate { + +class OverrideAlert : public BAlert { +public: + OverrideAlert(const char *title, const char *text, + const char *button1, uint32 modifiers1, + const char *button2, uint32 modifiers2, + const char *button3, uint32 modifiers3, + button_width width = B_WIDTH_AS_USUAL, + alert_type type = B_INFO_ALERT); + OverrideAlert(const char *title, const char *text, + const char *button1, uint32 modifiers1, + const char *button2, uint32 modifiers2, + const char *button3, uint32 modifiers3, + button_width width, button_spacing spacing, + alert_type type = B_INFO_ALERT); + virtual ~OverrideAlert(); + + virtual void DispatchMessage(BMessage *, BHandler *); + + static BPoint OverPosition(float width, float height); + +private: + void UpdateButtons(uint32 modifiers, bool force = false); + + uint32 fCurModifiers; + uint32 fButtonModifiers[3]; +}; + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/PendingNodeMonitorCache.cpp b/src/kits/tracker/PendingNodeMonitorCache.cpp new file mode 100644 index 0000000000..e75aa20ced --- /dev/null +++ b/src/kits/tracker/PendingNodeMonitorCache.cpp @@ -0,0 +1,141 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include "PendingNodeMonitorCache.h" +#include "PoseView.h" + +const bigtime_t kDelayedNodeMonitorLifetime = 10000000; + // after this much the pending node monitor gets discarded as + // too old + +PendingNodeMonitorEntry::PendingNodeMonitorEntry(const node_ref *node, + const BMessage *nodeMonitor) + : fExpiresAfter(system_time() + kDelayedNodeMonitorLifetime), + fNodeMonitor(*nodeMonitor), + fNode(*node) +{ +} + +const BMessage * +PendingNodeMonitorEntry::NodeMonitor() const +{ + return &fNodeMonitor; +} + +bool +PendingNodeMonitorEntry::Match(const node_ref *node) const +{ + return fNode == *node; +} + +bool +PendingNodeMonitorEntry::TooOld(bigtime_t now) const +{ + return now > fExpiresAfter; +} + + +PendingNodeMonitorCache::PendingNodeMonitorCache() + : fList(10, true) +{ +} + + +PendingNodeMonitorCache::~PendingNodeMonitorCache() +{ +} + +void +PendingNodeMonitorCache::Add(const BMessage *message) +{ +#if xDEBUG + PRINT(("adding pending node monitor\n")); + message->PrintToStream(); +#endif + node_ref node; + if (message->FindInt32("device", &node.device) != B_OK + || message->FindInt64("node", (int64 *)&node.node) != B_OK) + return; + + fList.AddItem(new PendingNodeMonitorEntry(&node, message)); +} + +void +PendingNodeMonitorCache::RemoveEntries(const node_ref *nodeRef) +{ + int32 count = fList.CountItems(); + for (int32 index = count - 1; index >= 0; index--) + if (fList.ItemAt(index)->Match(nodeRef)) + delete fList.RemoveItemAt(index); +} + +void +PendingNodeMonitorCache::RemoveOldEntries() +{ + bigtime_t now = system_time(); + int32 count = fList.CountItems(); + for (int32 index = count - 1; index >= 0; index--) + if (fList.ItemAt(index)->TooOld(now)) { + PRINT(("removing old entry from pending node monitor cache\n")); + delete fList.RemoveItemAt(index); + } +} + +void +PendingNodeMonitorCache::PoseCreatedOrMoved(BPoseView *poseView, const BPose *pose) +{ + bigtime_t now = system_time(); + int32 count = fList.CountItems(); + for (int32 index = 0; index < count;) { + PendingNodeMonitorEntry *item = fList.ItemAt(index); + if (item->TooOld(now)) { + PRINT(("removing old entry from pending node monitor cache\n")); + delete fList.RemoveItemAt(index); + count--; + } else if (item->Match(pose->TargetModel()->NodeRef())) { +#if DEBUG + PRINT(("reapplying node monitor for model:\n")); + pose->TargetModel()->PrintToStream(); + item->NodeMonitor()->PrintToStream(); + bool result = +#endif + poseView->FSNotification(item->NodeMonitor()); + ASSERT(result); + delete fList.RemoveItemAt(index); + count--; + } else + index++; + } +} + diff --git a/src/kits/tracker/PendingNodeMonitorCache.h b/src/kits/tracker/PendingNodeMonitorCache.h new file mode 100644 index 0000000000..3d56c28c43 --- /dev/null +++ b/src/kits/tracker/PendingNodeMonitorCache.h @@ -0,0 +1,89 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// PendingNodeMonitorCache is used to store node monitors that +// cannot be delivered yet because the corresponding model has not yet been +// added to a PoseView +// +// The respective node montior messages are stored in a list and applied +// later, when their target shows up. They get nuked when they become too +// old. + +#ifndef __PENDING_NODEMONITOR_CACHE_H__ +#define __PENDING_NODEMONITOR_CACHE_H__ + +#include +#include + +#include "ObjectList.h" + +namespace BPrivate { + +class BPoseView; +class BPose; + +class PendingNodeMonitorEntry { +public: + PendingNodeMonitorEntry(const node_ref *node, const BMessage *); + const BMessage *NodeMonitor() const; + bool Match(const node_ref *) const; + bool TooOld(bigtime_t now) const; + +private: + bigtime_t fExpiresAfter; + BMessage fNodeMonitor; + node_ref fNode; +}; + +class PendingNodeMonitorCache { +public: + PendingNodeMonitorCache(); + ~PendingNodeMonitorCache(); + + void Add(const BMessage *); + void RemoveEntries(const node_ref *); + void RemoveOldEntries(); + + void PoseCreatedOrMoved(BPoseView *, const BPose *); + +private: + BObjectList fList; +}; + +} // namespace BPrivate + +using namespace BPrivate; + +#endif + diff --git a/src/kits/tracker/Pose.cpp b/src/kits/tracker/Pose.cpp new file mode 100644 index 0000000000..a645194cdd --- /dev/null +++ b/src/kits/tracker/Pose.cpp @@ -0,0 +1,912 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include +#include + +#include +#include +#include + +#include "Attributes.h" +#include "Commands.h" +#include "FSClipboard.h" +#include "IconCache.h" +#include "Pose.h" +#include "PoseView.h" +#include "Utilities.h" + + +int32 +CalcFreeSpace(dev_t device) +{ + BVolume volume(device); + fs_info info; + if (volume.InitCheck() == B_OK && fs_stat_dev(device,&info) == B_OK) { + // Philosophy here: + // Bars go on all drives with read/write capabilities + // Exceptions: Not on CDDA, but on NTFS/Ext2 + // Also note that some volumes may return 0 when + // BVolume::Capacity() is called (believe-me... That *DOES* + // happen) so we also check for that. + off_t capacity = volume.Capacity(); + if (((!volume.IsReadOnly() && strcmp(info.fsh_name,"cdda")) + || !strcmp(info.fsh_name,"ntfs") + || !strcmp(info.fsh_name,"ext2")) + && (capacity > 0)) { + int32 percent = static_cast(volume.FreeBytes() / (capacity / 100)); + + // warn below 20 MB of free space (if this is less than 10% of free space) + if (volume.FreeBytes() < 20 * 1024 * 1024 && percent < 10) + return -2 - percent; + + return percent; + } + } + return -1; +} + + +// SymLink handling: +// symlink pose uses the resolved model to retrieve the icon, if not broken +// everything else, like the attributes, etc. is retrieved directly from the +// symlink itself + +BPose::BPose(Model *model, BPoseView *view, bool selected) + : fModel(model), + fWidgetList(4, true), + fPercent(-1), + fIsSelected(selected), + fDelayedEdit(true), + fHasLocation(false), + fNeedsSaveLocation(false), + fListModeInited(false), + fWasAutoPlaced(false), + fBrokenSymLink(false), + fBackgroundClean(false) +{ + CreateWidgets(view); + + if (model->IsVolume() && TrackerSettings().ShowVolumeSpaceBar()) { + dev_t device = model->NodeRef()->device; + fPercent = CalcFreeSpace(device); + } + + if ((fClipboardMode = FSClipboardFindNodeMode(model,true)) != 0 + && !view->HasPosesInClipboard()) { + view->SetHasPosesInClipboard(true); + } +} + + +BPose::~BPose() +{ + delete fModel; +} + + +void +BPose::CreateWidgets(BPoseView *poseView) +{ + for (int32 index = 0; ; index++) { + BColumn *column = poseView->ColumnAt(index); + if (!column) + break; + fWidgetList.AddItem(new BTextWidget(fModel, column, poseView)); + } +} + + +BTextWidget * +BPose::AddWidget(BPoseView *poseView, BColumn *column) +{ + BModelOpener opener(fModel); + if (fModel->InitCheck() != B_OK) + return NULL; + + BTextWidget *widget = new BTextWidget(fModel, column, poseView); + fWidgetList.AddItem(widget); + return widget; +} + + +BTextWidget * +BPose::AddWidget(BPoseView *poseView, BColumn *column, ModelNodeLazyOpener &opener) +{ + opener.OpenNode(); + if (fModel->InitCheck() != B_OK) + return NULL; + + BTextWidget *widget = new BTextWidget(fModel, column, poseView); + fWidgetList.AddItem(widget); + return widget; +} + + +void +BPose::RemoveWidget(BPoseView *, BColumn *column) +{ + int32 index; + BTextWidget *widget = WidgetFor(column->AttrHash(), &index); + if (widget) + delete fWidgetList.RemoveItemAt(index); +} + + +void +BPose::Commit(bool saveChanges, BPoint loc, BPoseView *poseView, int32 poseIndex) +{ + int32 count = fWidgetList.CountItems(); + for (int32 index = 0; index < count; index++) { + BTextWidget *widget = fWidgetList.ItemAt(index); + if (widget->IsActive()) { + widget->StopEdit(saveChanges, loc, poseView, this, poseIndex); + break; + } + } +} + + +inline bool +OneMouseUp(BTextWidget *widget, BPose *pose, BPoseView *poseView, BColumn *column, + BPoint poseLoc, BPoint where) +{ + BRect rect; + if (poseView->ViewMode() == kListMode) + rect = widget->CalcClickRect(poseLoc, column, poseView); + else + rect = widget->CalcClickRect(pose->Location(), 0, poseView); + + if (rect.Contains(where)) { + widget->MouseUp(rect, poseView, pose, where, pose->DelayedEdit()); + return true; + } + return false; +} + + +void +BPose::MouseUp(BPoint poseLoc, BPoseView *poseView, BPoint where, int32) +{ + WhileEachTextWidget(this, poseView, OneMouseUp, poseLoc, where); +} + + +inline void +OneCheckAndUpdate(BTextWidget *widget, BPose *, BPoseView *poseView, + BColumn *column, BPoint poseLoc) +{ + widget->CheckAndUpdate(poseLoc, column, poseView); +} + + +void +BPose::UpdateAllWidgets(int32, BPoint poseLoc, BPoseView *poseView) +{ + if (poseView->ViewMode() != kListMode) + poseLoc = fLocation; + + ASSERT(fModel->IsNodeOpen()); + EachTextWidget(this, poseView, OneCheckAndUpdate, poseLoc); +} + + +void +BPose::UpdateWidgetAndModel(Model *resolvedModel, const char *attrName, + uint32 attrType, int32, BPoint poseLoc, BPoseView *poseView) +{ + if (poseView->ViewMode() != kListMode) + poseLoc = fLocation; + + ASSERT(!resolvedModel || resolvedModel->IsNodeOpen()); + + if (attrName) { + // pick up new attributes and find out if icon needs updating + if (resolvedModel->AttrChanged(attrName)) + UpdateIcon(poseLoc, poseView); + + // ToDo: the following code is wrong, because this sort of hashing + // may overlap and we get aliasing + uint32 attrHash = AttrHashString(attrName, attrType); + BTextWidget *widget = WidgetFor(attrHash); + if (widget) { + BColumn *column = poseView->ColumnFor(attrHash); + if (column) + widget->CheckAndUpdate(poseLoc, column, poseView); + } else if (attrType == 0) { + // attribute got likely removed, so let's search the + // column for the matching attribute name + int32 count = fWidgetList.CountItems(); + for (int32 i = 0; i < count; i++) { + BTextWidget *widget = fWidgetList.ItemAt(i); + BColumn *column = poseView->ColumnFor(widget->AttrHash()); + if (column != NULL && !strcmp(column->AttrName(), attrName)) { + widget->CheckAndUpdate(poseLoc, column, poseView); + break; + } + } + } + } else { + // no attr name means check all widgets for stat info changes + + // pick up stat changes + if (resolvedModel && resolvedModel->StatChanged()) { + if (resolvedModel->InitCheck() != B_OK) + return; + + UpdateIcon(poseLoc, poseView); + } + + // distribute stat changes + for (int32 index = 0; ; index++) { + BColumn *column = poseView->ColumnAt(index); + if (!column) + break; + + if (column->StatField()) { + BTextWidget *widget = WidgetFor(column->AttrHash()); + if (widget) + widget->CheckAndUpdate(poseLoc, column, poseView); + } + } + } +} + + +bool +BPose::UpdateVolumeSpaceBar(bool enabled) +{ + if (!enabled) { + if (fPercent == -1) + return false; + + fPercent = -1; + return true; + } + + dev_t device = TargetModel()->NodeRef()->device; + int32 percent = CalcFreeSpace(device); + + if (fPercent != percent) { + if (percent > 100) + fPercent = 100; + else + fPercent = percent; + + return true; + } + return false; +} + + +void +BPose::UpdateIcon(BPoint poseLoc, BPoseView *poseView) +{ + IconCache::sIconCache->IconChanged(ResolvedModel()); + + BRect rect; + if (poseView->ViewMode() == kListMode) { + rect = CalcRect(poseLoc, poseView); + rect.left += kListOffset; + rect.right = rect.left + B_MINI_ICON; + rect.top = rect.bottom - B_MINI_ICON; + } else if (poseView->ViewMode() == kIconMode) { + rect.left = fLocation.x; + rect.top = fLocation.y; + rect.right = rect.left + B_LARGE_ICON; + rect.bottom = rect.top + B_LARGE_ICON; + } else { + rect.left = fLocation.x; + rect.top = fLocation.y; + rect.right = rect.left + B_MINI_ICON; + rect.bottom = rect.top + B_MINI_ICON; + } + + poseView->Invalidate(rect); +} + + +void +BPose::UpdateBrokenSymLink(BPoint poseLoc, BPoseView *poseView) +{ + ASSERT(TargetModel()->IsSymLink()); + ASSERT(!TargetModel()->LinkTo()); + UpdateIcon(poseLoc, poseView); +} + + +void +BPose::UpdateWasBrokenSymlink(BPoint poseLoc, BPoseView *poseView) +{ + if (!fModel->IsSymLink()) + return; + + if (fModel->LinkTo()) + return; + + poseView->CreateSymlinkPoseTarget(fModel); + if (!fModel->LinkTo()) + return; + + UpdateIcon(poseLoc, poseView); + fModel->LinkTo()->CloseNode(); +} + + +void +BPose::EditFirstWidget(BPoint poseLoc, BPoseView *poseView) +{ + // find first editable widget + BColumn *column; + for (int32 i = 0;(column = poseView->ColumnAt(i)) != NULL;i++) { + BTextWidget *widget = WidgetFor(column->AttrHash()); + + if (widget && widget->IsEditable()) { + BRect bounds; + // ToDo: + // fold the three StartEdit code sequences into a cover call + if (poseView->ViewMode() == kListMode) + bounds = widget->CalcRect(poseLoc, column, poseView); + else + bounds = widget->CalcRect(fLocation, NULL, poseView); + widget->StartEdit(bounds, poseView, this); + break; + } + } +} + + +void +BPose::EditPreviousNextWidgetCommon(BPoseView *poseView, bool next) +{ + bool found = false; + int32 delta = next ? 1 : -1; + for (int32 index = next ? 0 : poseView->CountColumns() - 1; ; index += delta) { + BColumn *column = poseView->ColumnAt(index); + if (!column) + break; + + BTextWidget *widget = WidgetFor(column->AttrHash()); + if (widget && widget->IsActive()) { + poseView->CommitActivePose(); + found = true; + continue; + } + + if (found && column->Editable()) { + BRect bounds; + if (poseView->ViewMode() == kListMode) { + int32 poseIndex = poseView->IndexOfPose(this); + BPoint poseLoc(0, poseIndex * poseView->ListElemHeight()); + bounds = widget->CalcRect(poseLoc, column, poseView); + } else + bounds = widget->CalcRect(fLocation, 0, poseView); + + widget->StartEdit(bounds, poseView, this); + break; + } + } +} + + +void +BPose::EditNextWidget(BPoseView *poseView) +{ + EditPreviousNextWidgetCommon(poseView, true); +} + + +void +BPose::EditPreviousWidget(BPoseView *poseView) +{ + EditPreviousNextWidgetCommon(poseView, false); +} + + +bool +BPose::PointInPose(const BPoseView *poseView, BPoint where) const +{ + ASSERT(poseView->ViewMode() != kListMode); + + if (poseView->ViewMode() == kIconMode) { + // check icon rect, then actual icon pixel + BRect rect(fLocation, fLocation); + rect.right += B_LARGE_ICON - 1; + rect.bottom += B_LARGE_ICON - 1; + + if (rect.Contains(where)) + return TestLargeIconPixel(where - fLocation); + + BTextWidget *widget = WidgetFor(poseView->FirstColumn()->AttrHash()); + if (widget) { + float textWidth = ceilf(widget->TextWidth(poseView) + 1); + rect.left += (B_LARGE_ICON - textWidth) / 2; + rect.right = rect.left + textWidth; + } + + rect.top = fLocation.y + B_LARGE_ICON; + rect.bottom = rect.top + poseView->FontHeight(); + + return rect.Contains(where); + } + + // MINI_ICON_MODE rect calc + BRect rect(fLocation, fLocation); + rect.right += B_MINI_ICON + kMiniIconSeparator; + rect.bottom += poseView->IconPoseHeight(); + BTextWidget *widget = WidgetFor(poseView->FirstColumn()->AttrHash()); + if (widget) + rect.right += ceil(widget->TextWidth(poseView) + 1); + + return rect.Contains(where); +} + + +bool +BPose::PointInPose(BPoint loc, const BPoseView *poseView, BPoint where, + BTextWidget **hitWidget) const +{ + if (hitWidget) + *hitWidget = NULL; + + // check intersection with icon + BRect rect; + rect.left = loc.x + kListOffset; + rect.right = rect.left + B_MINI_ICON; + rect.bottom = loc.y + poseView->ListElemHeight(); + rect.top = rect.bottom - B_MINI_ICON; + if (rect.Contains(where)) + return true; + + for (int32 index = 0; ; index++) { + BColumn *column = poseView->ColumnAt(index); + if (!column) + break; + BTextWidget *widget = WidgetFor(column->AttrHash()); + if (widget && widget->CalcClickRect(loc, column, poseView).Contains(where)) { + if (hitWidget) + *hitWidget = widget; + return true; + } + } + + return false; +} + + +void +BPose::Draw(BRect rect, BPoseView *poseView, BView *drawView, bool fullDraw, + const BRegion *updateRgn, BPoint offset, bool selected, bool recalculateText) +{ + if (fClipboardMode == kMoveSelectionTo) { + // If the background wasn't cleared and Draw() is not called after + // having edited a name or similar (with fullDraw) + if (!fBackgroundClean && !fullDraw) { + fBackgroundClean = true; + poseView->Invalidate(rect); + return; + } else + fBackgroundClean = false; + } + + bool directDraw = (drawView == poseView); + bool windowActive = poseView->Window()->IsActive(); + bool showSelectionWhenInactive = poseView->fShowSelectionWhenInactive; + bool isDrawingSelectionRect = poseView->fIsDrawingSelectionRect; + + ModelNodeLazyOpener modelOpener(fModel); + + if (poseView->ViewMode() == kListMode) { + BRect iconRect(rect); + iconRect.OffsetBy(offset); + iconRect.left += kListOffset; + iconRect.right = iconRect.left + B_MINI_ICON; + iconRect.top = iconRect.bottom - B_MINI_ICON; + if (!updateRgn || updateRgn->Intersects(iconRect)) + DrawIcon(iconRect.LeftTop(), drawView, B_MINI_ICON, directDraw, + !windowActive && !showSelectionWhenInactive); + + // draw text + for (int32 index = 0; ; index++) { + BColumn *column = poseView->ColumnAt(index); + if (!column) + break; + + // if widget doesn't exist, create it + BTextWidget *widget = WidgetFor(column, poseView, modelOpener); + + if (widget && widget->IsVisible()) { + BRect widgetRect(widget->ColumnRect(rect.LeftTop(), column, + poseView)); + + if (!updateRgn || updateRgn->Intersects(widgetRect)) { + BRect widgetTextRect(widget->CalcRect(rect.LeftTop(), column, + poseView)); + + if (recalculateText) + widget->RecalculateText(poseView); + + widget->Draw(widgetRect, widgetTextRect, column->Width(), + poseView, drawView, selected, fClipboardMode, offset, directDraw); + + if (index == 0 && selected) { + if (windowActive || isDrawingSelectionRect) { + widgetTextRect.OffsetBy(offset); + drawView->InvertRect(widgetTextRect); + } else if (!windowActive && showSelectionWhenInactive) { + widgetTextRect.OffsetBy(offset); + drawView->PushState(); + drawView->SetDrawingMode(B_OP_BLEND); + drawView->SetHighColor(128, 128, 128, 255); + drawView->FillRect(widgetTextRect); + drawView->PopState(); + } + } + } + } + + if (!fullDraw) + break; + } + } else { + + // draw in icon mode + if (updateRgn && !updateRgn->Intersects(rect)) + return; + + BPoint iconOrigin(fLocation); + iconOrigin += offset; + + DrawIcon(iconOrigin, drawView, poseView->ViewMode() == kIconMode ? + B_LARGE_ICON : B_MINI_ICON, directDraw, + !windowActive && !showSelectionWhenInactive && !poseView->IsDesktopWindow()); + + BColumn *column = poseView->FirstColumn(); + if (!column) + return; + + BTextWidget *widget = WidgetFor(column, poseView, modelOpener); + if (!widget || !widget->IsVisible()) + return; + + rect = widget->CalcRect(fLocation, 0, poseView); + + bool selectDuringDraw = directDraw && selected + && (poseView->IsDesktopWindow() + || (windowActive && !poseView->EraseWidgetTextBackground())); + + if (selectDuringDraw) { + // draw with dark background to select text + drawView->PushState(); + drawView->SetLowColor(0, 0, 0); + } + + widget->Draw(rect, rect, rect.Width(), poseView, drawView, + selected, fClipboardMode, offset, directDraw); + + if (selectDuringDraw) + drawView->PopState(); + else if (selected && directDraw) { + if (windowActive || isDrawingSelectionRect) { + rect.OffsetBy(offset); + drawView->InvertRect(rect); + } else if (!windowActive && showSelectionWhenInactive) { + drawView->PushState(); + drawView->SetDrawingMode(B_OP_BLEND); + drawView->SetHighColor(128, 128, 128, 255); + drawView->FillRect(rect); + drawView->PopState(); + } + } + } +} + + +void +BPose::DeselectWithoutErasingBackground(BRect, BPoseView *poseView) +{ + ASSERT(poseView->ViewMode() != kListMode); + ASSERT(!poseView->EraseWidgetTextBackground()); + ASSERT(!IsSelected()); + + // draw icon directly + if (fPercent == -1) + DrawIcon(fLocation, poseView, poseView->ViewMode() == kIconMode ? + B_LARGE_ICON : B_MINI_ICON, true); + else + UpdateIcon(fLocation,poseView); + + BColumn *column = poseView->FirstColumn(); + if (!column) + return; + + BTextWidget *widget = WidgetFor(column->AttrHash()); + if (!widget || !widget->IsVisible()) + return; + + // just invalidate the background, don't draw anything + poseView->Invalidate(widget->CalcRect(fLocation, 0, poseView)); +} + + +void +BPose::MoveTo(BPoint point, BPoseView *poseView, bool inval) +{ + point.x = floorf(point.x); + point.y = floorf(point.y); + + BRect oldBounds; + + ASSERT(poseView->ViewMode() != kListMode); + if (point == fLocation || poseView->ViewMode() == kListMode) + return; + + if (inval) + oldBounds = CalcRect(poseView); + + // might need to move a text view if we're active + if (poseView->ActivePose() == this) { + BView *border_view = poseView->FindView("BorderView"); + if (border_view) + border_view->MoveBy(point.x - fLocation.x, point.y - fLocation.y); + } + + fLocation = point; + fHasLocation = true; + fNeedsSaveLocation = true; + + if (inval) { + poseView->Invalidate(oldBounds); + poseView->Invalidate(CalcRect(poseView)); + } +} + + +BTextWidget * +BPose::ActiveWidget() const +{ + for (int32 i = fWidgetList.CountItems();i-- > 0;) { + BTextWidget *widget = fWidgetList.ItemAt(i); + if (widget->IsActive()) + return widget; + } + return NULL; +} + + +BTextWidget * +BPose::WidgetFor(uint32 attr, int32 *index) const +{ + int32 count = fWidgetList.CountItems(); + for (int32 i = 0; i < count; i++) { + BTextWidget *widget = fWidgetList.ItemAt(i); + if (widget->AttrHash() == attr) { + if (index) + *index = i; + return widget; + } + } + + return 0; +} + + +BTextWidget * +BPose::WidgetFor(BColumn *column, BPoseView *poseView, ModelNodeLazyOpener &opener, + int32 *index) +{ + BTextWidget *widget = WidgetFor(column->AttrHash(), index); + if (!widget) + widget = AddWidget(poseView, column, opener); + + return widget; +} + + +bool +BPose::TestLargeIconPixel(BPoint point) const +{ + return IconCache::sIconCache->IconHitTest(point, ResolvedModel(), + kNormalIcon, B_LARGE_ICON); +} + + +void +BPose::DrawIcon(BPoint where, BView *view, icon_size kind, bool direct, bool drawUnselected) +{ + if (fClipboardMode == kMoveSelectionTo) { + view->SetDrawingMode(B_OP_ALPHA); + view->SetHighColor(0,0,0,64); // set the level of transparency + view->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_OVERLAY); + } else if (direct) + view->SetDrawingMode(B_OP_OVER); + + IconCache::sIconCache->Draw(ResolvedModel(), view, where, + fIsSelected && !drawUnselected ? kSelectedIcon : kNormalIcon, kind, true); + + if (fPercent != -1) + DrawBar(where, view, kind); +} + + +void +BPose::DrawBar(BPoint where,BView *view,icon_size kind) +{ + view->PushState(); + + int32 size,barWidth,barHeight,yOffset; + if (kind == B_LARGE_ICON) { + size = B_LARGE_ICON - 1; + barWidth = 7; + yOffset = 2; + barHeight = size - 4 - 2*yOffset; + } else { + size = B_MINI_ICON; + barWidth = 4; + yOffset = 0; + barHeight = size - 4 - 2*yOffset; + } + + // the black shadowed line + view->SetHighColor(32,32,32,92); + view->MovePenTo(BPoint(where.x + size,where.y + 1 + yOffset)); + view->StrokeLine(BPoint(where.x + size,where.y + size - yOffset)); + view->StrokeLine(BPoint(where.x + size - barWidth + 1,where.y + size - yOffset)); + + view->SetDrawingMode(B_OP_ALPHA); + + // the gray frame + view->SetHighColor(76,76,76,192); + BRect rect( where.x + size - barWidth,where.y + yOffset, + where.x + size - 1,where.y + size - 1 - yOffset); + view->StrokeRect(rect); + + // calculate bar height + int32 percent = fPercent > -1 ? fPercent : -2 - fPercent; + int32 barPos = int32(barHeight * percent / 100.0); + if (barPos < 0) + barPos = 0; + else if (barPos > barHeight) + barPos = barHeight; + + // the free space bar + TrackerSettings settings; + view->SetHighColor(settings.FreeSpaceColor()); + + rect.InsetBy(1,1); + BRect bar(rect); + bar.bottom = bar.top + barPos - 1; + if (barPos > 0) + view->FillRect(bar); + + // the used space bar + bar.top = bar.bottom + 1; + bar.bottom = rect.bottom; + view->SetHighColor(fPercent < -1 ? settings.WarningSpaceColor() : settings.UsedSpaceColor()); + view->FillRect(bar); + + view->PopState(); +} + + +void +BPose::DrawToggleSwitch(BRect, BPoseView *) +{ + return; +} + + +BRect +BPose::CalcRect(BPoint loc, const BPoseView *poseView, bool minimalRect) +{ + ASSERT(poseView->ViewMode() == kListMode); + + BColumn *column = poseView->LastColumn(); + BRect rect; + rect.left = loc.x; + rect.top = loc.y; + rect.right = loc.x + column->Offset() + column->Width(); + rect.bottom = rect.top + poseView->ListElemHeight(); + + if (minimalRect) { + BTextWidget *widget = WidgetFor(poseView->FirstColumn()->AttrHash()); + if (widget) + rect.right = widget->CalcRect(loc, poseView->FirstColumn(), poseView).right; + } + + return rect; +} + + +BRect +BPose::CalcRect(const BPoseView *poseView) +{ + + ASSERT(poseView->ViewMode() != kListMode); + + BRect rect; + if (poseView->ViewMode() == kIconMode) { + rect.left = fLocation.x; + rect.right = rect.left + B_LARGE_ICON; + + BTextWidget *widget = WidgetFor(poseView->FirstColumn()->AttrHash()); + if (widget) { + float textWidth = ceilf(widget->TextWidth(poseView) + 1); + if (textWidth > B_LARGE_ICON) { + rect.left += (B_LARGE_ICON - textWidth) / 2; + rect.right = rect.left + textWidth; + } + } + + rect.top = fLocation.y; + rect.bottom = rect.top + poseView->IconPoseHeight(); + } else { + // MINI_ICON_MODE rect calc + rect.left = fLocation.x; + rect.top = fLocation.y; + rect.right = rect.left + B_MINI_ICON + kMiniIconSeparator; + rect.bottom = rect.top + poseView->IconPoseHeight(); + BTextWidget *widget = WidgetFor(poseView->FirstColumn()->AttrHash()); + if (widget) + rect.right += ceil(widget->TextWidth(poseView) + 1); + } + + return rect; +} + + +#if DEBUG + +void +BPose::PrintToStream() +{ + TargetModel()->PrintToStream(); + PRINT(("%sselected\n", IsSelected() ? "" : "not ")); + switch (fClipboardMode) { + case kMoveSelectionTo: + PRINT(("clipboardMode: Cut\n")); + break; + case kCopySelectionTo: + PRINT(("clipboardMode: Copy\n")); + break; + default: + PRINT(("clipboardMode: 0 - not in clipboard\n")); + } + PRINT(("location %s x:%f y:%f\n", HasLocation() ? "" : "unknown ", + HasLocation() ? Location().x : 0, + HasLocation() ? Location().y : 0)); + PRINT(("%sautoplaced \n", WasAutoPlaced() ? "was " : "not ")); +} + +#endif diff --git a/src/kits/tracker/Pose.h b/src/kits/tracker/Pose.h new file mode 100644 index 0000000000..2d74a164d9 --- /dev/null +++ b/src/kits/tracker/Pose.h @@ -0,0 +1,334 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef _POSE_H +#define _POSE_H + +#include + +#include "TextWidget.h" +#include "Model.h" +#include "Utilities.h" + +namespace BPrivate { + +class BPoseView; +class BTextWidget; + +enum { + B_NAME_WIDGET, + B_ALL_WIDGETS +}; + + +class BPose { + public: + BPose(Model *adopt, BPoseView *, bool selected = false); + virtual ~BPose(); + + BTextWidget *AddWidget(BPoseView *, BColumn *); + BTextWidget *AddWidget(BPoseView *, BColumn *, ModelNodeLazyOpener &opener); + void RemoveWidget(BPoseView *, BColumn *); + void SetLocation(BPoint); + void MoveTo(BPoint, BPoseView *, bool inval = true); + + void Draw(BRect, BPoseView *, bool fullDraw = true, const BRegion * = 0, + bool recalculateText = false); + void Draw(BRect, BPoseView *, BView *drawView, bool fullDraw, + const BRegion *, BPoint offset, bool selected, bool recalculateText = false); + void DeselectWithoutErasingBackground(BRect rect, BPoseView *poseView); + // special purpose draw call for deselecting over a textured + // background + + void DrawBar(BPoint where,BView *view,icon_size kind); + + void DrawIcon(BPoint, BView *, icon_size, bool direct, bool drawUnselected = false); + void DrawToggleSwitch(BRect, BPoseView *); + void MouseUp(BPoint poseLoc, BPoseView *, BPoint where, int32 index); + Model* TargetModel() const; + Model* ResolvedModel() const; + void Select(bool selected); + bool IsSelected() const; + // Rename to IsHighlighted + + BTextWidget *ActiveWidget() const; + BTextWidget *WidgetFor(uint32 hashAttr, int32 *index = 0) const; + BTextWidget *WidgetFor(BColumn *column, BPoseView *poseView, ModelNodeLazyOpener &opener, + int32 *index = NULL); + // adds the widget if needed + + bool PointInPose(BPoint poseLoc, const BPoseView *, BPoint where, + BTextWidget ** = NULL) const; + bool PointInPose(const BPoseView *, BPoint where) const ; + BRect CalcRect(BPoint loc, const BPoseView *, bool minimal_rect = false); + BRect CalcRect(const BPoseView *); + void UpdateAllWidgets(int32 poseIndex, BPoint poseLoc, BPoseView *); + void UpdateWidgetAndModel(Model *resolvedModel, const char *attrName, + uint32 attrType, int32 poseIndex, BPoint poseLoc, BPoseView *view); + bool UpdateVolumeSpaceBar(bool enabled); + void UpdateIcon(BPoint poseLoc, BPoseView *); + + //void UpdateFixedSymlink(BPoint poseLoc, BPoseView *); + void UpdateBrokenSymLink(BPoint poseLoc, BPoseView *); + void UpdateWasBrokenSymlink(BPoint poseLoc, BPoseView *poseView); + + void Commit(bool saveChanges, BPoint loc, BPoseView *, int32 index); + void EditFirstWidget(BPoint poseLoc, BPoseView *); + void EditNextWidget(BPoseView *); + void EditPreviousWidget(BPoseView *); + + BPoint Location() const; + bool DelayedEdit() const; + void SetDelayedEdit(bool delay); + bool ListModeInited() const; + bool HasLocation() const; + bool NeedsSaveLocation() const; + void SetSaveLocation(); + bool WasAutoPlaced() const; + void SetAutoPlaced(bool); + + uint32 ClipboardMode() const; + void SetClipboardMode(uint32 clipboardMode); + +#if DEBUG + void PrintToStream(); +#endif + + private: + void EditPreviousNextWidgetCommon(BPoseView *poseView, bool next); + void CreateWidgets(BPoseView *); + bool TestLargeIconPixel(BPoint) const; + + Model *fModel; + BObjectList fWidgetList; + BPoint fLocation; + + uint32 fClipboardMode; + int32 fPercent; + + bool fIsSelected : 1; + bool fDelayedEdit : 1; + bool fHasLocation : 1; + bool fNeedsSaveLocation : 1; + bool fListModeInited : 1; + bool fWasAutoPlaced : 1; + bool fBrokenSymLink : 1; + bool fBackgroundClean : 1; +}; + + +template +void +EachTextWidget(BPose *pose, BPoseView *poseView, + void (*func)(BTextWidget *, BPose *, BPoseView *, BColumn *, Param1), Param1 p1) +{ + for (int32 index = 0; ;index++) { + BColumn *column = poseView->ColumnAt(index); + if (!column) + break; + + BTextWidget *widget = pose->WidgetFor(column->AttrHash()); + if (widget) + (func)(widget, pose, poseView, column, p1); + } +} + + +template +void +EachTextWidget(BPose *pose, BPoseView *poseView, + void (*func)(BTextWidget *, BPose *, BPoseView *, BColumn *, + Param1, Param2), Param1 p1, Param2 p2) +{ + for (int32 index = 0; ;index++) { + BColumn *column = poseView->ColumnAt(index); + if (!column) + break; + + BTextWidget *widget = pose->WidgetFor(column->AttrHash()); + if (widget) + (func)(widget, pose, poseView, column, p1, p2); + } +} + + +template +Result +WhileEachTextWidget(BPose *pose, BPoseView *poseView, + Result (*func)(BTextWidget *, BPose *, BPoseView *, BColumn *, + Param1, Param2), Param1 p1, Param2 p2) +{ + for (int32 index = 0; ;index++) { + BColumn *column = poseView->ColumnAt(index); + if (!column) + break; + + BTextWidget *widget = pose->WidgetFor(column->AttrHash()); + if (widget) { + Result result = (func)(widget, pose, poseView, column, p1, p2); + if (result) + return result; + } + } + return 0; +} + + +inline Model * +BPose::TargetModel() const +{ + return fModel; +} + + +inline Model * +BPose::ResolvedModel() const +{ + return fModel->IsSymLink() ? + (fModel->LinkTo() ? fModel->LinkTo() : fModel) : fModel; +} + + +inline bool +BPose::IsSelected() const +{ + return fIsSelected; +} + + +inline void +BPose::Select(bool on) +{ + fIsSelected = on; +} + + +inline bool +BPose::DelayedEdit() const +{ + return fDelayedEdit; +} + + +inline void +BPose::SetDelayedEdit(bool on) +{ + fDelayedEdit = on; +} + + +inline bool +BPose::NeedsSaveLocation() const +{ + return fNeedsSaveLocation; +} + + +inline void +BPose::SetSaveLocation() +{ + fNeedsSaveLocation = true; +} + + +inline bool +BPose::ListModeInited() const +{ + return fListModeInited; +} + + +inline bool +BPose::WasAutoPlaced() const +{ + return fWasAutoPlaced; +} + + +inline void +BPose::SetAutoPlaced(bool on) +{ + fWasAutoPlaced = on; +} + + +inline bool +BPose::HasLocation() const +{ + return fHasLocation; +} + + +inline BPoint +BPose::Location() const +{ + return fLocation; +} + + +inline void +BPose::SetLocation(BPoint point) +{ + fLocation = BPoint(floorf(point.x), floorf(point.y)); + fHasLocation = true; +} + + +inline void +BPose::Draw(BRect rect, BPoseView *view, bool fullDraw, const BRegion *updateRgn, + bool recalculateText) +{ + Draw(rect, view, (BView *)view, fullDraw, updateRgn, BPoint(0, 0), + IsSelected(), recalculateText); +} + + +inline uint32 +BPose::ClipboardMode() const +{ + return fClipboardMode; +} + + +inline void +BPose::SetClipboardMode(uint32 clipboardMode) +{ + fClipboardMode = clipboardMode; +} + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/PoseList.cpp b/src/kits/tracker/PoseList.cpp new file mode 100644 index 0000000000..ddcfd086ce --- /dev/null +++ b/src/kits/tracker/PoseList.cpp @@ -0,0 +1,124 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// Icon cache is used for drawing node icons; it caches icons +// and reuses them for successive draws + +#include + +#include "PoseList.h" +#include "Pose.h" + + +BPose * +PoseList::FindPose(const node_ref *node, int32 *resultingIndex) const +{ + int32 count = CountItems(); + for (int32 index = 0; index < count; index++) { + BPose *pose = ItemAt(index); + ASSERT(pose->TargetModel()); + if (*pose->TargetModel()->NodeRef() == *node) { + if (resultingIndex) + *resultingIndex = index; + return pose; + } + } + return NULL; +} + +BPose * +PoseList::FindPose(const entry_ref *entry, int32 *resultingIndex) const +{ + int32 count = CountItems(); + for (int32 index = 0; index < count; index++) { + BPose *pose = ItemAt(index); + ASSERT(pose->TargetModel()); + if (*pose->TargetModel()->EntryRef() == *entry) { + if (resultingIndex) + *resultingIndex = index; + return pose; + } + } + return NULL; +} + +BPose * +PoseList::FindPose(const Model *model, int32 *resultingIndex) const +{ + return FindPose(model->NodeRef(), resultingIndex); +} + +BPose * +PoseList::DeepFindPose(const node_ref *node, int32 *resultingIndex) const +{ + int32 count = CountItems(); + for (int32 index = 0; index < count; index++) { + BPose *pose = ItemAt(index); + Model *model = pose->TargetModel(); + if (*model->NodeRef() == *node) { + if (resultingIndex) + *resultingIndex = index; + return pose; + } + // if model is a symlink, try matching node with the target + // of the link + if (model->IsSymLink()) { + model = model->LinkTo(); + if (model && *model->NodeRef() == *node) { + if (resultingIndex) + *resultingIndex = index; + return pose; + } + } + } + + return NULL; +} + +BPose * +PoseList::FindVolumePose(const dev_t device, int32 *resultingIndex) const +{ + int32 count = CountItems(); + for (int32 index = 0; index < count; index++) { + BPose *pose = ItemAt(index); + Model *model = pose->TargetModel(); + ASSERT(model); + if (model->IsVolume() && model->NodeRef()->device == device) { + if (resultingIndex) + *resultingIndex = index; + return pose; + } + } + return NULL; +} diff --git a/src/kits/tracker/PoseList.h b/src/kits/tracker/PoseList.h new file mode 100644 index 0000000000..9ed738f52c --- /dev/null +++ b/src/kits/tracker/PoseList.h @@ -0,0 +1,180 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// PoseList is a commonly used instance of BObjectList +// Defines convenience find and iteration calls + +#ifndef _POSE_LIST_H +#define _POSE_LIST_H + +#include "ObjectList.h" + +struct node_ref; +struct entry_ref; + +namespace BPrivate { + +class BPose; +class Model; + +class PoseList : public BObjectList { +public: + PoseList(int32 itemsPerBlock = 20, bool owning = false) + : BObjectList(itemsPerBlock, owning) + {} + + PoseList(const PoseList &list) + : BObjectList(list) + {} + + BPose *FindPose(const node_ref *node, int32 *index = NULL) const; + BPose *FindPose(const entry_ref *entry, int32 *index = NULL) const; + BPose *FindPose(const Model *model, int32 *index = NULL) const; + BPose *DeepFindPose(const node_ref *node, int32 *index = NULL) const; + // same as FindPose, node can be a target of the actual + // pose if the pose is a symlink + BPose *FindVolumePose(const dev_t device, int32 *index = NULL) const; +}; + +// iteration glue, add permutations as needed + +template +void +EachPoseAndModel(PoseList *list, void (*eachFunction)(BPose *, Model *, EachParam1), + EachParam1 eachParam1) +{ + for (int32 index = list->CountItems() - 1; index >= 0; index--) { + BPose *pose = list->ItemAt(index); + Model *model = pose->TargetModel(); + if (model) + (eachFunction)(pose, model, eachParam1); + } +} + +template +void +EachPoseAndModel(PoseList *list, void (*eachFunction)(BPose *, Model *, int32 , + EachParam1), EachParam1 eachParam1) +{ + for (int32 index = list->CountItems() - 1; index >= 0; index--) { + BPose *pose = list->ItemAt(index); + Model *model = pose->TargetModel(); + if (model) + (eachFunction)(pose, model, index, eachParam1); + } +} + +template +void +EachPoseAndModel(PoseList *list, void (*eachFunction)(BPose *, Model *, EachParam1, + EachParam2), EachParam1 eachParam1, EachParam2 eachParam2) +{ + for (int32 index = list->CountItems() - 1; index >= 0; index--) { + BPose *pose = list->ItemAt(index); + Model *model = pose->TargetModel(); + if (model) + (eachFunction)(pose, model, eachParam1, eachParam2); + } +} + +template +void +EachPoseAndModel(PoseList *list, void (*eachFunction)(BPose *, Model *, int32, + EachParam1, EachParam2), EachParam1 eachParam1, EachParam2 eachParam2) +{ + for (int32 index = list->CountItems() - 1; index >= 0; index--) { + BPose *pose = list->ItemAt(index); + Model *model = pose->TargetModel(); + if (model) + (eachFunction)(pose, model, index, eachParam1, eachParam2); + } +} + +template +void +EachPoseAndResolvedModel(PoseList *list, void (*eachFunction)(BPose *, Model *, EachParam1), + EachParam1 eachParam1) +{ + for (int32 index = list->CountItems() - 1; index >= 0; index--) { + BPose *pose = list->ItemAt(index); + Model *model = pose->TargetModel()->ResolveIfLink(); + if (model) + (eachFunction)(pose, model, eachParam1); + } +} + +template +void +EachPoseAndResolvedModel(PoseList *list, void (*eachFunction)(BPose *, Model *, int32 , + EachParam1), EachParam1 eachParam1) +{ + for (int32 index = list->CountItems() - 1; index >= 0; index--) { + BPose *pose = list->ItemAt(index); + Model *model = pose->TargetModel()->ResolveIfLink(); + if (model) + (eachFunction)(pose, model, index, eachParam1); + } +} + +template +void +EachPoseAndResolvedModel(PoseList *list, void (*eachFunction)(BPose *, Model *, EachParam1, + EachParam2), EachParam1 eachParam1, EachParam2 eachParam2) +{ + for (int32 index = list->CountItems() - 1; index >= 0; index--) { + BPose *pose = list->ItemAt(index); + Model *model = pose->TargetModel()->ResolveIfLink(); + if (model) + (eachFunction)(pose, model, eachParam1, eachParam2); + } +} + +template +void +EachPoseAndResolvedModel(PoseList *list, void (*eachFunction)(BPose *, Model *, int32, + EachParam1, EachParam2), EachParam1 eachParam1, EachParam2 eachParam2) +{ + for (int32 index = list->CountItems() - 1; index >= 0; index--) { + BPose *pose = list->ItemAt(index); + Model *model = pose->TargetModel()->ResolveIfLink(); + if (model) + (eachFunction)(pose, model, index, eachParam1, eachParam2); + } +} + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/PoseView.cpp b/src/kits/tracker/PoseView.cpp new file mode 100644 index 0000000000..8062db88de --- /dev/null +++ b/src/kits/tracker/PoseView.cpp @@ -0,0 +1,9255 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Attributes.h" +#include "AttributeStream.h" +#include "AutoLock.h" +#include "BackgroundImage.h" +#include "Bitmaps.h" +#include "Commands.h" +#include "ContainerWindow.h" +#include "CountView.h" +#include "DeskWindow.h" +#include "DesktopPoseView.h" +#include "DirMenu.h" +#include "EntryIterator.h" +#include "FilePanelPriv.h" +#include "FSClipboard.h" +#include "FSUtils.h" +#include "FunctionObject.h" +#include "MimeTypes.h" +#include "Navigator.h" +#include "NavMenu.h" +#include "Pose.h" +#include "PoseView.h" +#include "InfoWindow.h" +#include "Utilities.h" +#include "Tests.h" +#include "TextViewSupport.h" +#include "Thread.h" +#include "Tracker.h" +#include "TrackerString.h" +#include "WidgetAttributeText.h" + + +const float kDoubleClickTresh = 6; +const float kCountViewWidth = 62; + +const uint32 kAddNewPoses = 'Tanp'; +const int32 kMaxAddPosesChunk = 10; + +namespace BPrivate { +extern bool delete_point(void *); + // ToDo: exterminate this +} + +const float kSlowScrollBucket = 30; +const float kBorderHeight = 20; + +enum { + kAutoScrollOff, + kWaitForTransition, + kDelayAutoScroll, + kAutoScrollOn +}; + +enum { + kWasDragged, + kContextMenuShown, + kNotDragged +}; + +enum { + kInsertAtFront, + kInsertAfter +}; + +const BPoint kTransparentDragThreshold(256, 192); + // maximum size of the transparent drag bitmap, use a drag rect + // if larger in any direction + +const char *kNoCopyToTrashStr = "Sorry, you can't copy items to the Trash."; +const char *kNoLinkToTrashStr = "Sorry, you can't create links in the Trash."; +const char *kNoCopyToRootStr = "You must drop items on one of the disk icons " + "in the \"Disks\" window."; +const char *kOkToMoveStr = "Are you sure you want to move or copy the selected " + "item(s) to this folder?"; + +struct AddPosesResult { + ~AddPosesResult(); + void ReleaseModels(); + + Model *fModels[kMaxAddPosesChunk]; + PoseInfo fPoseInfos[kMaxAddPosesChunk]; + int32 fCount; +}; + + +AddPosesResult::~AddPosesResult(void) +{ + for (int32 i = 0; i < fCount; i++) + delete fModels[i]; +} + + +void +AddPosesResult::ReleaseModels(void) +{ + for (int32 i = 0; i < kMaxAddPosesChunk; i++) + fModels[i] = NULL; +} + + +// #pragma mark - + + +BPoseView::BPoseView(Model *model, BRect bounds, uint32 viewMode, uint32 resizeMask) + : BView(bounds, "PoseView", resizeMask, B_WILL_DRAW | B_PULSE_NEEDED), + fIsDrawingSelectionRect(false), + fHScrollBar(NULL), + fVScrollBar(NULL), + fModel(model), + fActivePose(NULL), + fExtent(LONG_MAX, LONG_MAX, LONG_MIN, LONG_MIN), + fPoseList(new PoseList(40, true)), + fVSPoseList(new PoseList()), + fSelectionList(new PoseList()), + fMimeTypesInSelectionCache(20, true), + fZombieList(new BObjectList(10, true)), + fColumnList(new BObjectList(4, true)), + fMimeTypeList(new BObjectList(10, true)), + fMimeTypeListIsDirty(false), + fViewState(new BViewState), + fStateNeedsSaving(false), + fCountView(NULL), + fUpdateRegion(new BRegion), // does this need to be allocated ?? + fDropTarget(NULL), + fDropTargetWasSelected(false), + fSelectionHandler(be_app), + fLastClickPt(LONG_MAX, LONG_MAX), + fLastClickTime(0), + fLastClickedPose(NULL), + fLastExtent(LONG_MAX, LONG_MAX, LONG_MIN, LONG_MIN), + fTitleView(NULL), + fRefFilter(NULL), + fAutoScrollInc(20), + fAutoScrollState(kAutoScrollOff), + fEraseWidgetBackground(true), + fSelectionPivotPose(NULL), + fRealPivotPose(NULL), + fKeyRunner(NULL), + fSelectionVisible(true), + fMultipleSelection(true), + fDragEnabled(true), + fDropEnabled(true), + fSelectionRectEnabled(true), + fAlwaysAutoPlace(false), + fAllowPoseEditing(true), + fSelectionChangedHook(false), + fSavePoseLocations(true), + fShowHideSelection(true), + fOkToMapIcons(true), + fEnsurePosesVisible(false), + fShouldAutoScroll(true), + fIsDesktopWindow(false), + fIsWatchingDateFormatChange(false), + fHasPosesInClipboard(false) +{ + + fViewState->SetViewMode(viewMode); + fShowSelectionWhenInactive = TrackerSettings().ShowSelectionWhenInactive(); + fTransparentSelection = TrackerSettings().TransparentSelection(); +} + + +BPoseView::~BPoseView() +{ + delete fPoseList; + delete fVSPoseList; + delete fColumnList; + delete fSelectionList; + delete fMimeTypeList; + delete fZombieList; + delete fUpdateRegion; + delete fViewState; + delete fModel; + delete fKeyRunner; + + IconCache::sIconCache->Deleting(this); +} + + +void +BPoseView::Init(AttributeStreamNode *node) +{ + RestoreState(node); + InitCommon(); +} + + +void +BPoseView::Init(const BMessage &message) +{ + RestoreState(message); + InitCommon(); +} + + +void +BPoseView::InitCommon() +{ + BContainerWindow *window = ContainerWindow(); + + // create title view for window + BRect rect(Frame()); + rect.bottom = rect.top + kTitleViewHeight; + fTitleView = new BTitleView(rect, this); + if (ViewMode() == kListMode) { + // resize and move poseview + MoveBy(0, kTitleViewHeight + 1); + ResizeBy(0, -(kTitleViewHeight + 1)); + + if (Parent()) + Parent()->AddChild(fTitleView); + else + Window()->AddChild(fTitleView); + } + + if (fHScrollBar) + fHScrollBar->SetTitleView(fTitleView); + + BPoint origin; + if (ViewMode() == kListMode) + origin = fViewState->ListOrigin(); + else + origin = fViewState->IconOrigin(); + + PinPointToValidRange(origin); + + // init things related to laying out items + fListElemHeight = ceilf(fFontHeight < 20 ? 20 : fFontHeight + 6); + SetIconPoseHeight(); + GetLayoutInfo(ViewMode(), &fGrid, &fOffset); + ResetPosePlacementHint(); + + DisableScrollBars(); + ScrollTo(origin); + UpdateScrollRange(); + SetScrollBarsTo(origin); + EnableScrollBars(); + + StartWatching(); + // trun on volume node monitor, metamime monitor, etc. + + if (window && window->ShouldAddCountView()) + AddCountView(); + + // populate the window + if (window && window->IsTrash()) + AddTrashPoses(); + else + AddPoses(TargetModel()); + + UpdateScrollRange(); +} + + +static int +CompareColumns(const BColumn *c1, const BColumn *c2) +{ + if (c1->Offset() > c2->Offset()) + return 1; + else if (c1->Offset() < c2->Offset()) + return -1; + + return 0; +} + + +void +BPoseView::RestoreColumnState(AttributeStreamNode *node) +{ + fColumnList->MakeEmpty(); + if (node) { + const char *columnsAttr; + const char *columnsAttrForeign; + if (TargetModel() && TargetModel()->IsRoot()) { + columnsAttr = kAttrDisksColumns; + columnsAttrForeign = kAttrDisksColumnsForeign; + } else { + columnsAttr = kAttrColumns; + columnsAttrForeign = kAttrColumnsForeign; + } + + bool wrongEndianness = false; + const char *name = columnsAttr; + size_t size = (size_t)node->Contains(name, B_RAW_TYPE); + if (!size) { + name = columnsAttrForeign; + wrongEndianness = true; + size = (size_t)node->Contains(name, B_RAW_TYPE); + } + + if (size > 0 && size < 10000) { + // check for invalid sizes here to protect against munged attributes + char *buffer = new char[size]; + off_t result = node->Read(name, 0, B_RAW_TYPE, size, buffer); + if (result) { + BMallocIO stream; + stream.WriteAt(0, buffer, size); + stream.Seek(0, SEEK_SET); + + // Clear old column list if neccessary + + // Put items in the list in order so they can be checked + // for overlaps below. + BObjectList tempSortedList; + for (;;) { + BColumn *column = BColumn::InstantiateFromStream(&stream, + wrongEndianness); + if (!column) + break; + tempSortedList.AddItem(column); + } + AddColumnList(&tempSortedList); + } + delete [] buffer; + } + } + SetUpDefaultColumnsIfNeeded(); + if (!ColumnFor(PrimarySort())) { + fViewState->SetPrimarySort(FirstColumn()->AttrHash()); + fViewState->SetPrimarySortType(FirstColumn()->AttrType()); + } + + if (PrimarySort() == SecondarySort()) + fViewState->SetSecondarySort(0); +} + + +void +BPoseView::RestoreColumnState(const BMessage &message) +{ + fColumnList->MakeEmpty(); + + BObjectList tempSortedList; + for (int32 index = 0; ; index++) { + BColumn *column = BColumn::InstantiateFromMessage(message, index); + if (!column) + break; + tempSortedList.AddItem(column); + } + + AddColumnList(&tempSortedList); + + SetUpDefaultColumnsIfNeeded(); + if (!ColumnFor(PrimarySort())) { + fViewState->SetPrimarySort(FirstColumn()->AttrHash()); + fViewState->SetPrimarySortType(FirstColumn()->AttrType()); + } + + if (PrimarySort() == SecondarySort()) + fViewState->SetSecondarySort(0); +} + + +void +BPoseView::AddColumnList(BObjectList *list) +{ + list->SortItems(&CompareColumns); + + float nextLeftEdge = 0; + for (int32 columIndex = 0; columIndex < list->CountItems(); columIndex++) { + BColumn *column = list->ItemAt(columIndex); + + // Make sure that columns don't overlap + if (column->Offset() < nextLeftEdge) { + PRINT(("\t**Overlapped columns in archived column state\n")); + column->SetOffset(nextLeftEdge); + } + + nextLeftEdge = column->Offset() + column->Width() + + kTitleColumnExtraMargin; + fColumnList->AddItem(column); + + if (!IsWatchingDateFormatChange() && column->AttrType() == B_TIME_TYPE) + StartWatchDateFormatChange(); + } +} + + +void +BPoseView::RestoreState(AttributeStreamNode *node) +{ + RestoreColumnState(node); + + if (node) { + const char *viewStateAttr; + const char *viewStateAttrForeign; + + if (TargetModel() && TargetModel()->IsRoot()) { + viewStateAttr = kAttrDisksViewState; + viewStateAttrForeign = kAttrDisksViewStateForeign; + } else { + viewStateAttr = kAttrViewState; + viewStateAttrForeign = kAttrViewStateForeign; + } + + bool wrongEndianness = false; + const char *name = viewStateAttr; + size_t size = (size_t)node->Contains(name, B_RAW_TYPE); + if (!size) { + name = viewStateAttrForeign; + wrongEndianness = true; + size = (size_t)node->Contains(name, B_RAW_TYPE); + } + + if (size > 0 && size < 10000) { + // check for invalid sizes here to protect against munged attributes + char *buffer = new char[size]; + off_t result = node->Read(name, 0, B_RAW_TYPE, size, buffer); + if (result) { + BMallocIO stream; + stream.WriteAt(0, buffer, size); + stream.Seek(0, SEEK_SET); + BViewState *viewstate = BViewState::InstantiateFromStream(&stream, + wrongEndianness); + if (viewstate) { + delete fViewState; + fViewState = viewstate; + } + } + delete [] buffer; + } + } + + if (IsDesktopWindow() && ViewMode() == kListMode) + // recover if desktop window view state set wrong + fViewState->SetViewMode(kIconMode); +} + + +void +BPoseView::RestoreState(const BMessage &message) +{ + RestoreColumnState(message); + + BViewState *viewstate = BViewState::InstantiateFromMessage(message); + + if (viewstate) { + delete fViewState; + fViewState = viewstate; + } + + if (IsDesktopWindow() && ViewMode() == kListMode) { + // recover if desktop window view state set wrong + fViewState->SetViewMode(kIconMode); + } +} + + +namespace BPrivate { + +bool +ClearViewOriginOne(const char *DEBUG_ONLY(name), uint32 type, off_t size, + void *viewStateArchive, void *) +{ + ASSERT(strcmp(name, kAttrViewState) == 0); + + if (!viewStateArchive) + return false; + + if (type != B_RAW_TYPE) + return false; + + BMallocIO stream; + stream.WriteAt(0, viewStateArchive, (size_t)size); + stream.Seek(0, SEEK_SET); + BViewState *viewstate = BViewState::InstantiateFromStream(&stream, false); + if (!viewstate) + return false; + + // this is why we are here - zero out + viewstate->SetListOrigin(BPoint(0, 0)); + viewstate->SetIconOrigin(BPoint(0, 0)); + + stream.Seek(0, SEEK_SET); + viewstate->ArchiveToStream(&stream); + stream.ReadAt(0, viewStateArchive, (size_t)size); + + return true; +} + +} // namespace BPrivate + + +void +BPoseView::SetUpDefaultColumnsIfNeeded() +{ + // in case there were errors getting some columns + if (fColumnList->CountItems() != 0) + return; + + fColumnList->AddItem(new BColumn("Name", kColumnStart, 145, B_ALIGN_LEFT, + kAttrStatName, B_STRING_TYPE, true, true)); + fColumnList->AddItem(new BColumn("Size", 200, 80, B_ALIGN_RIGHT, + kAttrStatSize, B_OFF_T_TYPE, true, false)); + fColumnList->AddItem(new BColumn("Modified", 295, 150, B_ALIGN_LEFT, + kAttrStatModified, B_TIME_TYPE, true, false)); + + if (!IsWatchingDateFormatChange()) + StartWatchDateFormatChange(); +} + + +void +BPoseView::SaveColumnState(AttributeStreamNode *node) +{ + BMallocIO stream; + for (int32 index = 0; ; index++) { + const BColumn *column = ColumnAt(index); + if (!column) + break; + column->ArchiveToStream(&stream); + } + const char *columnsAttr; + const char *columnsAttrForeign; + if (TargetModel() && TargetModel()->IsRoot()) { + columnsAttr = kAttrDisksColumns; + columnsAttrForeign = kAttrDisksColumnsForeign; + } else { + columnsAttr = kAttrColumns; + columnsAttrForeign = kAttrColumnsForeign; + } + node->Write(columnsAttr, columnsAttrForeign, B_RAW_TYPE, + stream.Position(), stream.Buffer()); +} + + +void +BPoseView::SaveColumnState(BMessage &message) const +{ + for (int32 index = 0; ; index++) { + const BColumn *column = ColumnAt(index); + if (!column) + break; + column->ArchiveToMessage(message); + } +} + + +void +BPoseView::SaveState(AttributeStreamNode *node) +{ + SaveColumnState(node); + + // save view state into object + BMallocIO stream; + + if (ViewMode() == kListMode) + fViewState->SetListOrigin(LeftTop()); + else + fViewState->SetIconOrigin(LeftTop()); + + stream.Seek(0, SEEK_SET); + fViewState->ArchiveToStream(&stream); + + const char *viewStateAttr; + const char *viewStateAttrForeign; + if (TargetModel() && TargetModel()->IsRoot()) { + viewStateAttr = kAttrDisksViewState; + viewStateAttrForeign = kAttrDisksViewStateForeign; + } else { + viewStateAttr = kAttrViewState; + viewStateAttrForeign = kAttrViewStateForeign; + } + + node->Write(viewStateAttr, viewStateAttrForeign, B_RAW_TYPE, + stream.Position(), stream.Buffer()); + + fStateNeedsSaving = false; + fViewState->MarkSaved(); +} + + +void +BPoseView::SaveState(BMessage &message) const +{ + SaveColumnState(message); + + if (ViewMode() == kListMode) + fViewState->SetListOrigin(LeftTop()); + else + fViewState->SetIconOrigin(LeftTop()); + + fViewState->ArchiveToMessage(message); +} + + +float +BPoseView::StringWidth(const char *str) const +{ + return fWidthBuf->StringWidth(str, 0, (int32)strlen(str), &fCurrentFont); +} + + +float +BPoseView::StringWidth(const char *str, int32 len) const +{ + ASSERT(strlen(str) == (uint32)len); + return fWidthBuf->StringWidth(str, 0, len, &fCurrentFont); +} + + +void +BPoseView::SavePoseLocations(BRect *frameIfDesktop) +{ + PoseInfo poseInfo; + + if (!fSavePoseLocations) + return; + + ASSERT(TargetModel()); + ASSERT(Window()->IsLocked()); + + BVolume volume(TargetModel()->NodeRef()->device); + if (volume.InitCheck() != B_OK) + return; + + if (!TargetModel()->IsRoot() + && (volume.IsReadOnly() || !volume.KnowsAttr())) { + // check that we can write out attrs; Root should always work + // because it gets saved on the boot disk but the above checks + // will fail + return; + } + + bool desktop = IsDesktopWindow() && (frameIfDesktop != NULL); + + int32 count = fPoseList->CountItems(); + for (int32 index = 0; index < count; index++) { + BPose *pose = fPoseList->ItemAt(index); + if (pose->NeedsSaveLocation() && pose->HasLocation()) { + Model *model = pose->TargetModel(); + poseInfo.fInvisible = false; + + if (model->IsRoot()) + poseInfo.fInitedDirectory = TargetModel()->NodeRef()->node; + else + poseInfo.fInitedDirectory = model->EntryRef()->directory; + + poseInfo.fLocation = pose->Location(); + + ExtendedPoseInfo *extendedPoseInfo = NULL; + size_t extendedPoseInfoSize = 0; + ModelNodeLazyOpener opener(model, true); + + if (desktop) { + opener.OpenNode(true); + // if saving desktop icons, save an extended pose info too + extendedPoseInfo = ReadExtendedPoseInfo(model); + // read the pre-existing one + + if (!extendedPoseInfo) { + // don't have one yet, allocate one + size_t size = ExtendedPoseInfo::Size(1); + extendedPoseInfo = (ExtendedPoseInfo *) + new char [size]; + + memset(extendedPoseInfo, 0, size); + extendedPoseInfo->fWorkspaces = 0xffffffff; + extendedPoseInfo->fInvisible = false; + extendedPoseInfo->fShowFromBootOnly = false; + extendedPoseInfo->fNumFrames = 0; + } + ASSERT(extendedPoseInfo); + + extendedPoseInfo->SetLocationForFrame(pose->Location(), + *frameIfDesktop); + extendedPoseInfoSize = extendedPoseInfo->Size(); + } + + if (model->InitCheck() != B_OK) + continue; + + ASSERT(model); + ASSERT(model->InitCheck() == B_OK); + // special handling for "root" disks icon + if (model->IsRoot()) { + BVolume bootVol; + BDirectory dir; + BVolumeRoster().GetBootVolume(&bootVol); + if (FSGetDeskDir(&dir, bootVol.Device()) == B_OK) { + if (dir.WriteAttr(kAttrDisksPoseInfo, B_RAW_TYPE, 0, + &poseInfo, sizeof(poseInfo)) == sizeof(poseInfo)) + // nuke opposite endianness + dir.RemoveAttr(kAttrDisksPoseInfoForeign); + + if (desktop && dir.WriteAttr(kAttrExtendedDisksPoseInfo, + B_RAW_TYPE, 0, + extendedPoseInfo, extendedPoseInfoSize) + == (ssize_t)extendedPoseInfoSize) + // nuke opposite endianness + dir.RemoveAttr(kAttrExtendedDisksPoseInfoForegin); + } + } else { + model->WriteAttrKillForegin(kAttrPoseInfo, kAttrPoseInfoForeign, + B_RAW_TYPE, 0, &poseInfo, sizeof(poseInfo)); + + if (desktop) + model->WriteAttrKillForegin(kAttrExtendedPoseInfo, + kAttrExtendedPoseInfoForegin, + B_RAW_TYPE, 0, extendedPoseInfo, extendedPoseInfoSize); + } + + delete [] (char *)extendedPoseInfo; + // ToDo: + // fix up this mess + } + } +} + + +void +BPoseView::StartWatching() +{ + // watch volumes + TTracker::WatchNode(0, B_WATCH_MOUNT, this); + BMimeType::StartWatching(BMessenger(this)); +} + + +void +BPoseView::StopWatching() +{ + stop_watching(this); + BMimeType::StopWatching(BMessenger(this)); +} + + +void +BPoseView::DetachedFromWindow() +{ + if (fTitleView && !fTitleView->Window()) + delete fTitleView; + + if (TTracker *app = dynamic_cast(be_app)) { + app->Lock(); + app->StopWatching(this, kShowSelectionWhenInactiveChanged); + app->StopWatching(this, kTransparentSelectionChanged); + app->StopWatching(this, kSortFolderNamesFirstChanged); + app->StopWatching(this, kShowVolumeSpaceBar); + app->StopWatching(this, kSpaceBarColorChanged); + app->StopWatching(this, kUpdateVolumeSpaceBar); + app->Unlock(); + } + + StopWatching(); + CommitActivePose(); + SavePoseLocations(); + + FSClipboardStopWatch(this); +} + + +void +BPoseView::Pulse() +{ + BContainerWindow *window = ContainerWindow(); + if (!window) + return; + + window->PulseTaskLoop(); + // make sure task loop gets pulsed properly, if installed + + // update item count view in window if necessary + UpdateCount(); + + if (fAutoScrollState != kAutoScrollOff) + HandleAutoScroll(); + + // do we need to update scrollbars? + BRect extent = Extent(); + if ((fLastExtent != extent) || (fLastLeftTop != LeftTop())) { + uint32 button; + BPoint mouse; + GetMouse(&mouse, &button); + if (!button) { + UpdateScrollRange(); + fLastExtent = extent; + fLastLeftTop = LeftTop(); + } + } +} + + +void +BPoseView::MoveBy(float x, float y) +{ + if (fTitleView && fTitleView->Window()) + fTitleView->MoveBy(x, y); + + _inherited::MoveBy(x, y); +} + + +void +BPoseView::AttachedToWindow() +{ + fIsDesktopWindow = (dynamic_cast(Window()) != 0); + if (fIsDesktopWindow) + AddFilter(new TPoseViewFilter(this)); + + AddFilter(new ShortcutFilter(B_RETURN, B_OPTION_KEY, kOpenSelection, this)); + // add Option-Return as a shortcut filter because AddShortcut doesn't allow + // us to have shortcuts without Command yet + AddFilter(new ShortcutFilter(B_ESCAPE, 0, B_CANCEL, this)); + // Escape key, currently used only to abort an on-going clipboard cut + AddFilter(new ShortcutFilter(B_ESCAPE, B_SHIFT_KEY, kCancelSelectionToClipboard, this)); + // Escape + SHIFT will remove current selection from clipboard, or all poses from current folder if 0 selected + + fLastLeftTop = LeftTop(); + BFont font(be_plain_font); + font.SetSpacing(B_BITMAP_SPACING); + SetFont(&font); + GetFont(&fCurrentFont); + + // static - init just once + if (fFontHeight == -1) { + font.GetHeight(&fFontInfo); + fFontHeight = fFontInfo.ascent + fFontInfo.descent + fFontInfo.leading; + } + + if (TTracker *app = dynamic_cast(be_app)) { + app->Lock(); + app->StartWatching(this, kShowSelectionWhenInactiveChanged); + app->StartWatching(this, kTransparentSelectionChanged); + app->StartWatching(this, kSortFolderNamesFirstChanged); + app->StartWatching(this, kShowVolumeSpaceBar); + app->StartWatching(this, kSpaceBarColorChanged); + app->StartWatching(this, kUpdateVolumeSpaceBar); + app->Unlock(); + } + + FSClipboardStartWatch(this); +} + + +void +BPoseView::SetIconPoseHeight() +{ + switch (ViewMode()) { + case kIconMode: + fIconPoseHeight = ceilf(B_LARGE_ICON + fFontHeight + 1); + break; + + case kMiniIconMode: + fIconPoseHeight = ceilf(fFontHeight < B_MINI_ICON ? B_MINI_ICON : fFontHeight + 1); + break; + + default: + fIconPoseHeight = fListElemHeight; + break; + } +} + + +void +BPoseView::GetLayoutInfo(uint32 mode, BPoint *grid, BPoint *offset) const +{ + switch (mode) { + case kMiniIconMode: + grid->Set(96, 20); + offset->Set(10, 5); + break; + + case kIconMode: + grid->Set(60, 60); + offset->Set(20, 20); + break; + + default: + offset->Set(5, 5); + grid->Set(0, 0); + break; + } +} + + +void +BPoseView::MakeFocus(bool focused) +{ + bool inval = false; + if (focused != IsFocus()) + inval = true; + + _inherited::MakeFocus(focused); + + if (inval) { + BackgroundView *view = dynamic_cast(Parent()); + if (view) + view->PoseViewFocused(focused); + } +} + + +void +BPoseView::WindowActivated(bool activated) +{ + if (activated == false) + CommitActivePose(); + + if (fShowHideSelection) + ShowSelection(activated); + + if (activated && !ActivePose() && !IsFilePanel()) + MakeFocus(); +} + + +void +BPoseView::SetActivePose(BPose *pose) +{ + if (pose != ActivePose()) { + CommitActivePose(); + fActivePose = pose; + } +} + + +void +BPoseView::CommitActivePose(bool saveChanges) +{ + if (ActivePose()) { + int32 index = fPoseList->IndexOf(ActivePose()); + BPoint loc(0, index * fListElemHeight); + if (ViewMode() != kListMode) + loc = ActivePose()->Location(); + + ActivePose()->Commit(saveChanges, loc, this, index); + fActivePose = NULL; + } +} + + +EntryListBase * +BPoseView::InitDirentIterator(const entry_ref *ref) +{ + // set up a directory iteration + Model sourceModel(ref, false, true); + if (sourceModel.InitCheck() != B_OK) + return NULL; + + ASSERT(!sourceModel.IsQuery()); + ASSERT(sourceModel.Node()); + ASSERT(dynamic_cast(sourceModel.Node())); + + EntryListBase *result = new CachedDirectoryEntryList( + *dynamic_cast(sourceModel.Node())); + + if (result->Rewind() != B_OK) { + delete result; + HideBarberPole(); + return NULL; + } + + TTracker::WatchNode(sourceModel.NodeRef(), B_WATCH_DIRECTORY + | B_WATCH_NAME | B_WATCH_STAT | B_WATCH_ATTR, this); + + return result; +} + + +uint32 +BPoseView::WatchNewNodeMask() +{ + return B_WATCH_STAT | B_WATCH_ATTR; +} + + +status_t +BPoseView::WatchNewNode(const node_ref *item) +{ + return WatchNewNode(item, WatchNewNodeMask(), BMessenger(this)); +} + + +status_t +BPoseView::WatchNewNode(const node_ref *item, uint32 mask, BMessenger messenger) +{ + status_t result = TTracker::WatchNode(item, mask, messenger); + +#if DEBUG + if (result != B_OK) + PRINT(("failed to watch node %s\n", strerror(result))); +#endif + + return result; +} + + +struct AddPosesParams { + BMessenger target; + entry_ref ref; +}; + + +bool +BPoseView::IsValidAddPosesThread(thread_id currentThread) const +{ + return fAddPosesThreads.find(currentThread) != fAddPosesThreads.end(); +} + + +void +BPoseView::AddPoses(Model *model) +{ + // if model is zero, PoseView has other means of iterating through all + // the entries that it adds + if (model) { + TrackerSettings settings; + if (model->IsRoot()) { + AddRootPoses(true, settings.MountSharedVolumesOntoDesktop()); + return; + } else if (IsDesktopView() + && (settings.MountVolumesOntoDesktop() + || (IsFilePanel() && settings.DesktopFilePanelRoot()))) + AddRootPoses(true, settings.MountSharedVolumesOntoDesktop()); + } + + ShowBarberPole(); + + AddPosesParams *params = new AddPosesParams(); + BMessenger tmp(this); + params->target = tmp; + + if (model) + params->ref = *model->EntryRef(); + + thread_id addPosesThread = spawn_thread(&BPoseView::AddPosesTask, "add poses", + B_DISPLAY_PRIORITY, params); + + if (addPosesThread >= B_OK) { + fAddPosesThreads.insert(addPosesThread); + resume_thread(addPosesThread); + } else + delete params; +} + + +class AutoLockingMessenger { + // Note: + // this locker requires that you lock/unlock the messenger and associated + // looper only through the autolocker interface, otherwise the hasLock + // flag gets out of sync + // + // Also, this class represents the entire BMessenger, not just it's + // autolocker (unlike MessengerAutoLocker) + public: + AutoLockingMessenger(const BMessenger &target, bool lockLater = false) + : messenger(target), + hasLock(false) + { + if (!lockLater) + hasLock = messenger.LockTarget(); + } + + ~AutoLockingMessenger() + { + if (hasLock) { + BLooper *looper; + messenger.Target(&looper); + ASSERT(looper->IsLocked()); + looper->Unlock(); + } + } + + bool Lock() + { + if (!hasLock) + hasLock = messenger.LockTarget(); + + return hasLock; + } + + bool IsLocked() const + { + return hasLock; + } + + void Unlock() + { + if (hasLock) { + BLooper *looper; + messenger.Target(&looper); + ASSERT(looper); + looper->Unlock(); + hasLock = false; + } + } + + BLooper *Looper() const + { + BLooper *looper; + messenger.Target(&looper); + return looper; + } + + BHandler *Handler() const + { + ASSERT(hasLock); + return messenger.Target(0); + } + + BMessenger Target() const + { + return messenger; + } + + private: + BMessenger messenger; + bool hasLock; +}; + + +class failToLock { /* exception in AddPoses*/ }; + + +status_t +BPoseView::AddPosesTask(void *castToParams) +{ + // AddPosesTask reeds a bunch of models and passes them off to + // the pose placing and drawing routine. + // + AddPosesParams *params = (AddPosesParams *)castToParams; + BMessenger target(params->target); + entry_ref ref(params->ref); + + delete params; + + AutoLockingMessenger lock(target); + + if (!lock.IsLocked()) + return B_ERROR; + + thread_id threadID = find_thread(NULL); + + BPoseView *view = dynamic_cast(lock.Handler()); + ASSERT(view); + + // BWindow *window = dynamic_cast(lock.Looper()); + ASSERT(dynamic_cast(lock.Looper())); + + // allocate the iterator we will use for adding poses; this + // can be a directory or any other collection of entry_refs, such + // as results of a query; subclasses override this to provide + // other than standard directory iterations + EntryListBase *container = view->InitDirentIterator(&ref); + if (!container) { + view->HideBarberPole(); + return B_ERROR; + } + + AddPosesResult *posesResult = new AddPosesResult; + posesResult->fCount = 0; + int32 modelChunkIndex = 0; + bigtime_t nextChunkTime = 0; + uint32 watchMask = view->WatchNewNodeMask(); + + bool hideDotFiles = TrackerSettings().HideDotFiles(); + +#if DEBUG + for (int32 index = 0; index < kMaxAddPosesChunk; index++) + posesResult->fModels[index] = (Model *)0xdeadbeef; +#endif + + try { + for (;;) { + lock.Unlock(); + + status_t result = B_OK; + char entBuf[1024]; + dirent *eptr = (dirent *)entBuf; + Model *model = 0; + node_ref dirNode; + node_ref itemNode; + + posesResult->fModels[modelChunkIndex] = 0; + // ToDo - redo this so that modelChunkIndex increments right before + // a new model is added to the array; start with modelChunkIndex = -1 + + int32 count = container->GetNextDirents(eptr, 1024, 1); + if (count <= 0 && !modelChunkIndex) + break; + + if (count) { + ASSERT(count == 1); + + if ((!hideDotFiles && (!strcmp(eptr->d_name, ".") || !strcmp(eptr->d_name, ".."))) + || (hideDotFiles && eptr->d_name[0] == '.')) + continue; + + dirNode.device = eptr->d_pdev; + dirNode.node = eptr->d_pino; + itemNode.device = eptr->d_dev; + itemNode.node = eptr->d_ino; + + BPoseView::WatchNewNode(&itemNode, watchMask, lock.Target()); + // have to node monitor ahead of time because Model will + // cache up the file type and preferred app + // OK to call when poseView is not locked + model = new Model(&dirNode, &itemNode, eptr->d_name, true); + result = model->InitCheck(); + posesResult->fModels[modelChunkIndex] = model; + } + + // before we access the pose view, lock down the window + + if (!lock.Lock()) { + PRINT(("failed to lock\n")); + posesResult->fCount = modelChunkIndex + 1; + throw failToLock(); + } + + if (!view->IsValidAddPosesThread(threadID)) { + // this handles the case of a file panel when the directory is switched + // and and old AddPosesTask needs to die. + // we might no longer be the current async thread + // for this view - if not then we're done + view->HideBarberPole(); + + // for now use the same cleanup as failToLock does + posesResult->fCount = modelChunkIndex + 1; + throw failToLock(); + } + + if (count) { + // try to watch the model, no matter what + + if (result != B_OK) { + // failed to init pose, model is a zombie, add to zombie list + PRINT(("1 adding model %s to zombie list, error %s\n", model->Name(), + strerror(model->InitCheck()))); + view->fZombieList->AddItem(model); + continue; + } + + view->ReadPoseInfo(model, &(posesResult->fPoseInfos[modelChunkIndex])); + if (!view->ShouldShowPose(model, &(posesResult->fPoseInfos[modelChunkIndex])) + // filter out models we do not want to show + || model->IsSymLink() && !view->CreateSymlinkPoseTarget(model)) { + // filter out symlinks whose target models we do not + // want to show + + posesResult->fModels[modelChunkIndex] = 0; + delete model; + continue; + } + // ToDo: + // we are only watching nodes that are visible and not zombies + // EntryCreated watches everything, which is probably more correct + // clean this up + + modelChunkIndex++; + } + + bigtime_t now = system_time(); + + if (!count || modelChunkIndex >= kMaxAddPosesChunk || now > nextChunkTime) { + // keep getting models until we get of them + // or until 300000 runs out + + ASSERT(modelChunkIndex > 0); + + // send of the created poses + + posesResult->fCount = modelChunkIndex; + BMessage creationData(kAddNewPoses); + creationData.AddPointer("currentPoses", posesResult); + creationData.AddRef("ref", &ref); + + lock.Target().SendMessage(&creationData); + + modelChunkIndex = 0; + nextChunkTime = now + 300000; + + posesResult = new AddPosesResult; + posesResult->fCount = 0; + } + + if (!count) + break; + } + } catch (failToLock) { + // we are here because the window got closed or otherwise failed to + // lock + + PRINT(("add_poses cleanup \n")); + // failed to lock window, bail + delete posesResult; + delete container; + + return B_ERROR; + } + + ASSERT(!modelChunkIndex); + + delete posesResult; + delete container; + // build attributes menu based on mime types we've added + + if (lock.Lock()) { + view->AddPosesCompleted(); +#ifdef MSIPL_COMPILE_H + // workaround for broken PPC STL, not needed with the SGI headers for x86 + set::iterator i = view->fAddPosesThreads.find(threadID); + if (i != view->fAddPosesThreads.end()) + view->fAddPosesThreads.erase(i); +#else + view->fAddPosesThreads.erase(threadID); +#endif + } + + return B_OK; +} + + +void +BPoseView::AddRootPoses(bool watchIndividually, bool mountShared) +{ + BVolumeRoster roster; + roster.Rewind(); + BVolume volume; + + if (TrackerSettings().ShowDisksIcon() && !TargetModel()->IsRoot()) { + BEntry entry("/"); + Model model(&entry); + if (model.InitCheck() == B_OK) { + BMessage monitorMsg; + monitorMsg.what = B_NODE_MONITOR; + + monitorMsg.AddInt32("opcode", B_ENTRY_CREATED); + + monitorMsg.AddInt32("device", model.NodeRef()->device); + monitorMsg.AddInt64("node", model.NodeRef()->node); + monitorMsg.AddInt64("directory", model.EntryRef()->directory); + monitorMsg.AddString("name", model.EntryRef()->name); + if (Window()) + Window()->PostMessage(&monitorMsg, this); + } + } else { + while (roster.GetNextVolume(&volume) == B_OK) { + if (!volume.IsPersistent()) + continue; + + if (volume.IsShared() && !mountShared) + continue; + + CreateVolumePose(&volume, watchIndividually); + } + } + + SortPoses(); + UpdateCount(); + Invalidate(); +} + + +void +BPoseView::RemoveRootPoses() +{ + int32 index; + int32 count = fPoseList->CountItems(); + for (index = 0; index < count;) { + BPose *pose = fPoseList->ItemAt(index); + if (pose) { + Model *model = pose->TargetModel(); + if (model) { + if (model->IsVolume()) { + DeletePose(model->NodeRef()); + count--; + } else + index++; + } + } + } + + SortPoses(); + UpdateCount(); + Invalidate(); +} + + +void +BPoseView::AddTrashPoses() +{ + // the trash window needs to display a union of all the + // trash folders from all the mounted volumes + BVolumeRoster volRoster; + volRoster.Rewind(); + BVolume volume; + while (volRoster.GetNextVolume(&volume) == B_OK) { + if (!volume.IsPersistent()) + continue; + + BDirectory trashDir; + BEntry entry; + if (FSGetTrashDir(&trashDir, volume.Device()) == B_OK + && trashDir.GetEntry(&entry) == B_OK) { + Model model(&entry); + if (model.InitCheck() == B_OK) + AddPoses(&model); + } + } +} + + +void +BPoseView::AddPosesCompleted() +{ + BContainerWindow *containerWindow = ContainerWindow(); + if (containerWindow) + containerWindow->AddMimeTypesToMenu(); + + // if we're not in icon mode then we need to check for poses that + // were "auto" placed to see if they overlap with other icons + if (ViewMode() != kListMode) + CheckAutoPlacedPoses(); + + HideBarberPole(); + + // make sure that the last item in the list is not placed + // above the top of the view (leaving you with an empty window) + if (ViewMode() == kListMode) { + BRect bounds(Bounds()); + float lastItemTop = (fPoseList->CountItems() - 1) * fListElemHeight; + if (bounds.top > lastItemTop) + ScrollTo(bounds.left, max_c(lastItemTop, 0)); + } +} + + +void +BPoseView::CreateVolumePose(BVolume *volume, bool watchIndividually) +{ + if (volume->InitCheck() != B_OK || !volume->IsPersistent()) { + // We never want to create poses for those volumes; the file + // system root, /pipe, /dev, etc. are all non-persistent + return; + } + + BDirectory root; + if (volume->GetRootDirectory(&root) == B_OK) { + node_ref itemNode; + root.GetNodeRef(&itemNode); + + BEntry entry; + root.GetEntry(&entry); + + entry_ref ref; + entry.GetRef(&ref); + + node_ref dirNode; + dirNode.device = ref.device; + dirNode.node = ref.directory; + + BPose *pose = EntryCreated(&dirNode, &itemNode, ref.name, 0); + + if (pose && watchIndividually) { + // make sure volume names still get watched, even though + // they are on the desktop which is not their physical parent + pose->TargetModel()->WatchVolumeAndMountPoint(B_WATCH_NAME | B_WATCH_STAT + | B_WATCH_ATTR, this); + } + } +} + + +BPose * +BPoseView::CreatePose(Model *model, PoseInfo *poseInfo, bool insertionSort, + int32 *indexPtr, BRect *boundsPtr, bool forceDraw) +{ + BPose *result; + CreatePoses(&model, poseInfo, 1, &result, insertionSort, indexPtr, + boundsPtr, forceDraw); + return result; +} + + +void +BPoseView::FinishPendingScroll(float &listViewScrollBy, BRect bounds) +{ + if (!listViewScrollBy) + return; + + BRect srcRect(bounds); + BRect dstRect = srcRect; + srcRect.bottom -= listViewScrollBy; + dstRect.top += listViewScrollBy; + CopyBits(srcRect, dstRect); + listViewScrollBy = 0; + srcRect.bottom = dstRect.top; + SynchronousUpdate(srcRect); +} + + +bool +BPoseView::AddPosesThreadValid(const entry_ref *ref) const +{ + return *(TargetModel()->EntryRef()) == *ref || ContainerWindow()->IsTrash(); +} + + +void +BPoseView::CreatePoses(Model **models, PoseInfo *poseInfoArray, int32 count, + BPose **resultingPoses, bool insertionSort, int32 *lastPoseIndexPtr, + BRect *boundsPtr, bool forceDraw) +{ + // were we passed the bounds of the view? + BRect viewBounds; + if (boundsPtr) + viewBounds = *boundsPtr; + else + viewBounds = Bounds(); + + int32 poseIndex = 0; + float listViewScrollBy = 0; + for (int32 modelIndex = 0; modelIndex < count; modelIndex++) { + Model *model = models[modelIndex]; + + if (FindPose(model) || FindZombie(model->NodeRef())) { + // we already have this pose, don't add it + watch_node(model->NodeRef(), B_STOP_WATCHING, this); + delete model; + if (resultingPoses) + resultingPoses[modelIndex] = NULL; + continue; + } + + ASSERT(model->IsNodeOpen()); + PoseInfo *poseInfo = &poseInfoArray[modelIndex]; + + // pose adopts model and deletes it when done + BPose *pose = new BPose(model, this); + + if (resultingPoses) + resultingPoses[modelIndex] = pose; + + AddMimeType(model->MimeType()); + // set location from poseinfo if saved loc was for this dir + if (poseInfo->fInitedDirectory != -1LL) { + PinPointToValidRange(poseInfo->fLocation); + pose->SetLocation(poseInfo->fLocation); + AddToVSList(pose); + } + + BRect poseBounds; + + switch (ViewMode()) { + case kListMode: + { + poseIndex = fPoseList->CountItems(); + + bool havePoseBounds = false; + bool addedItem = false; + + if (insertionSort && fPoseList->CountItems()) { + int32 orientation = BSearchList(pose, &poseIndex); + + if (orientation == kInsertAfter) + poseIndex++; + + poseBounds = CalcPoseRect(pose, poseIndex); + havePoseBounds = true; + BRect srcRect(Extent()); + srcRect.top = poseBounds.top; + srcRect = srcRect & viewBounds; + BRect destRect(srcRect); + destRect.OffsetBy(0, fListElemHeight); + + if (srcRect.Intersects(viewBounds) + || destRect.Intersects(viewBounds)) { + if (srcRect.top == viewBounds.top + && srcRect.bottom == viewBounds.bottom) { + // if new pose above current view bounds, cache up + // the draw and do it later + listViewScrollBy += fListElemHeight; + forceDraw = false; + } else { + FinishPendingScroll(listViewScrollBy, viewBounds); + fPoseList->AddItem(pose, poseIndex); + fMimeTypeListIsDirty = true; + addedItem = true; + CopyBits(srcRect, destRect); + srcRect.bottom = destRect.top; + + //SynchronousUpdate(srcRect); + Invalidate(srcRect); + } + } + } + if (!addedItem) { + fPoseList->AddItem(pose, poseIndex); + fMimeTypeListIsDirty = true; + } + + if (forceDraw) { + if (!havePoseBounds) + poseBounds = CalcPoseRect(pose, poseIndex); + if (viewBounds.Intersects(poseBounds)) + Invalidate(poseBounds); + } + break; + } + + case kIconMode: + case kMiniIconMode: + if (poseInfo->fInitedDirectory == -1LL || fAlwaysAutoPlace) { + if (pose->HasLocation()) + RemoveFromVSList(pose); + + PlacePose(pose, viewBounds); + + // we set a flag in the pose here to signify that we were + // auto placed - after adding all poses to window, we're + // going to go back and make sure that the auto placed poses + // don't overlap previously positioned icons. If so, we'll + // move them to new positions. + if (!fAlwaysAutoPlace) + pose->SetAutoPlaced(true); + + AddToVSList(pose); + } + + // add item to list and draw if necessary + fPoseList->AddItem(pose); + fMimeTypeListIsDirty = true; + + poseBounds = pose->CalcRect(this); + + if (fEnsurePosesVisible && !viewBounds.Intersects(poseBounds)) { + viewBounds.InsetBy(20, 20); + RemoveFromVSList(pose); + BPoint loc(pose->Location()); + loc.ConstrainTo(viewBounds); + pose->SetLocation(loc); + pose->SetSaveLocation(); + AddToVSList(pose); + poseBounds = pose->CalcRect(this); + viewBounds.InsetBy(-20, -20); + } + + if (forceDraw && viewBounds.Intersects(poseBounds)) + Invalidate(poseBounds); + + // if this is the first item then we set extent here + if (fPoseList->CountItems() == 1) + fExtent = poseBounds; + else + AddToExtent(poseBounds); + + break; + } + if (model->IsSymLink()) + model->ResolveIfLink()->CloseNode(); + + model->CloseNode(); + } + + FinishPendingScroll(listViewScrollBy, viewBounds); + + if (lastPoseIndexPtr) + *lastPoseIndexPtr = poseIndex; +} + + + +bool +BPoseView::PoseVisible(const Model *model, const PoseInfo *poseInfo, + bool inFilePanel) +{ + return (!poseInfo->fInvisible + || (inFilePanel && strcmp(model->Name(), B_DESKTOP_DIR_NAME))); +} + + +bool +BPoseView::ShouldShowPose(const Model *model, const PoseInfo *poseInfo) +{ + if (!PoseVisible(model, poseInfo, IsFilePanel())) + return false; + + // check filter before adding item + return !fRefFilter || fRefFilter->Filter(model->EntryRef(), model->Node(), + const_cast(model->StatBuf()), model->MimeType()); +} + + +const char * +BPoseView::MimeTypeAt(int32 index) +{ + if (fMimeTypeListIsDirty) + RefreshMimeTypeList(); + + return fMimeTypeList->ItemAt(index)->String(); +} + + +int32 +BPoseView::CountMimeTypes() +{ + if (fMimeTypeListIsDirty) + RefreshMimeTypeList(); + + return fMimeTypeList->CountItems(); +} + + +void +BPoseView::AddMimeType(const char *mimeType) +{ + if (fMimeTypeListIsDirty) + RefreshMimeTypeList(); + + int32 count = fMimeTypeList->CountItems(); + for (int32 index = 0; index < count; index++) { + if (*fMimeTypeList->ItemAt(index) == mimeType) + return; + } + + fMimeTypeList->AddItem(new BString(mimeType)); +} + + +void +BPoseView::RefreshMimeTypeList() +{ + fMimeTypeList->MakeEmpty(); + fMimeTypeListIsDirty = false; + + for (int32 index = 0;; index++) { + BPose *pose = PoseAtIndex(index); + if (!pose) + break; + + if (pose->TargetModel()) + AddMimeType(pose->TargetModel()->MimeType()); + } +} + + +void +BPoseView::InsertPoseAfter(BPose *pose, int32 *index, int32 orientation, + BRect *invalidRect) +{ + if (orientation == kInsertAfter) { + // ToDo: + // get rid of this + (*index)++; + } + + BRect bounds(Bounds()); + // copy the good bits in the list + BRect srcRect(Extent()); + srcRect.top = CalcPoseRect(pose, *index).top; + srcRect = srcRect & bounds; + BRect destRect(srcRect); + destRect.OffsetBy(0, fListElemHeight); + + if (srcRect.Intersects(bounds) || destRect.Intersects(bounds)) + CopyBits(srcRect, destRect); + + // this is the invalid rectangle + srcRect.bottom = destRect.top; + *invalidRect = srcRect; +} + + +void +BPoseView::DisableScrollBars() +{ + if (fHScrollBar) + fHScrollBar->SetTarget((BView *)NULL); + if (fVScrollBar) + fVScrollBar->SetTarget((BView *)NULL); +} + + +void +BPoseView::EnableScrollBars() +{ + if (fHScrollBar) + fHScrollBar->SetTarget(this); + if (fVScrollBar) + fVScrollBar->SetTarget(this); +} + + +void +BPoseView::AddScrollBars() +{ + AutoLock lock(Window()); + if (!lock) + return; + + BRect bounds(Frame()); + + // horizontal + BRect rect(bounds); + rect.top = rect.bottom + 1; + rect.bottom = rect.top + (float)B_H_SCROLL_BAR_HEIGHT; + rect.right++; + fHScrollBar = new BHScrollBar(rect, "HScrollBar", this); + if (Parent()) + Parent()->AddChild(fHScrollBar); + else + Window()->AddChild(fHScrollBar); + + // vertical + rect = bounds; + rect.left = rect.right + 1; + rect.right = rect.left + (float)B_V_SCROLL_BAR_WIDTH; + rect.bottom++; + fVScrollBar = new BScrollBar(rect, "VScrollBar", this, 0, 100, B_VERTICAL); + if (Parent()) + Parent()->AddChild(fVScrollBar); + else + Window()->AddChild(fVScrollBar); +} + + +void +BPoseView::UpdateCount() +{ + if (fCountView) + fCountView->CheckCount(); +} + + +void +BPoseView::AddCountView() +{ + AutoLock lock(Window()); + if (!lock) + return; + + BRect rect(Frame()); + rect.right = rect.left + kCountViewWidth; + rect.top = rect.bottom + 1; + rect.bottom = rect.top + (float)B_H_SCROLL_BAR_HEIGHT - 1; + fCountView = new BCountView(rect, this); + if (Parent()) + Parent()->AddChild(fCountView); + else + Window()->AddChild(fCountView); + + if (fHScrollBar) { + fHScrollBar->MoveBy(kCountViewWidth+1, 0); + fHScrollBar->ResizeBy(-kCountViewWidth-1, 0); + } +} + + +void +BPoseView::MessageReceived(BMessage *message) +{ + if (message->WasDropped() && HandleMessageDropped(message)) + return; + + if (HandleScriptingMessage(message)) + return; + + switch (message->what) { + case kContextMenuDragNDrop: + { + BContainerWindow *window = ContainerWindow(); + if (window && window->Dragging()) { + BPoint droppoint, dropoffset; + if (message->FindPoint("_drop_point_", &droppoint) == B_OK) { + BMessage* dragmessage = window->DragMessage(); + dragmessage->FindPoint("click_pt", &dropoffset); + dragmessage->AddPoint("_drop_point_", droppoint); + dragmessage->AddPoint("_drop_offset_", dropoffset); + HandleMessageDropped(dragmessage); + } + DragStop(); + } + break; + } + + case kAddNewPoses: + { + AddPosesResult *currentPoses; + entry_ref ref; + message->FindPointer("currentPoses", reinterpret_cast(¤tPoses)); + message->FindRef("ref", &ref); + + // check if CreatePoses should be called (abort if dir has been switched + // under normal circumstances, ignore in several special cases + if (AddPosesThreadValid(&ref)) { + CreatePoses(currentPoses->fModels, currentPoses->fPoseInfos, + currentPoses->fCount, NULL, true, 0, 0, true); + currentPoses->ReleaseModels(); + } + delete currentPoses; + break; + } + + case kRestoreBackgroundImage: + ContainerWindow()->UpdateBackgroundImage(); + break; + + case B_META_MIME_CHANGED: + NoticeMetaMimeChanged(message); + break; + + case B_NODE_MONITOR: + case B_QUERY_UPDATE: + if (!FSNotification(message)) + pendingNodeMonitorCache.Add(message); + break; + + case kListMode: + case kIconMode: + case kMiniIconMode: + SetViewMode(message->what); + break; + + case B_SELECT_ALL: + { + // Select widget if there is an active one + BTextWidget *widget; + if (ActivePose() && ((widget = ActivePose()->ActiveWidget())) != 0) + widget->SelectAll(this); + else + SelectAll(); + break; + } + + case B_CUT: + FSClipboardAddPoses(TargetModel()->NodeRef(), fSelectionList, kMoveSelectionTo, true); + break; + + case kCutMoreSelectionToClipboard: + FSClipboardAddPoses(TargetModel()->NodeRef(), fSelectionList, kMoveSelectionTo, false); + break; + + case B_COPY: + FSClipboardAddPoses(TargetModel()->NodeRef(), fSelectionList, kCopySelectionTo, true); + break; + + case kCopyMoreSelectionToClipboard: + FSClipboardAddPoses(TargetModel()->NodeRef(), fSelectionList, kCopySelectionTo, false); + break; + + case B_PASTE: + FSClipboardPaste(TargetModel()); + break; + + case kPasteLinksFromClipboard: + FSClipboardPaste(TargetModel(), kCreateLink); + break; + + case B_CANCEL: + if (FSClipboardHasRefs()) + FSClipboardClear(); + break; + + case kCancelSelectionToClipboard: + FSClipboardRemovePoses(TargetModel()->NodeRef(), (fSelectionList->CountItems() > 0 ? fSelectionList : fPoseList)); + break; + + case kFSClipboardChanges: + { + node_ref node; + message->FindInt32("device", &node.device); + message->FindInt64("directory", &node.node); + + if (*TargetModel()->NodeRef() == node) + UpdatePosesClipboardModeFromClipboard(message); + else if (message->FindBool("clearClipboard") + && HasPosesInClipboard()) { + // just remove all poses from clipboard + SetHasPosesInClipboard(false); + SetPosesClipboardMode(0); + } + break; + } + + case kInvertSelection: + InvertSelection(); + break; + + case kShowSelectionWindow: + ShowSelectionWindow(); + break; + + case kDuplicateSelection: + DuplicateSelection(); + break; + + case kOpenSelection: + OpenSelection(); + break; + + case kOpenSelectionWith: + OpenSelectionUsing(); + break; + + case kRestoreFromTrash: + RestoreSelectionFromTrash(); + break; + + case kDelete: + if (ContainerWindow()->IsTrash()) + // if trash delete instantly + DeleteSelection(true, false); + else + DeleteSelection(); + break; + + case kMoveToTrash: + { + TrackerSettings settings; + + if ((modifiers() & B_SHIFT_KEY) != 0 || settings.DontMoveFilesToTrash()) + DeleteSelection(true, settings.AskBeforeDeleteFile()); + else + MoveSelectionToTrash(); + break; + } + + case kCleanupAll: + Cleanup(true); + break; + + case kCleanup: + Cleanup(); + break; + + case kEditQuery: + EditQueries(); + break; + + case kRunAutomounterSettings: + be_app->PostMessage(message); + break; + + case kNewEntryFromTemplate: + if (message->HasRef("refs_template")) + NewFileFromTemplate(message); + break; + + case kNewFolder: + NewFolder(message); + break; + + case kUnmountVolume: + UnmountSelectedVolumes(); + break; + + case kEmptyTrash: + FSEmptyTrash(); + break; + + case kGetInfo: + OpenInfoWindows(); + break; + + case kIdentifyEntry: + IdentifySelection(); + break; + + case kEditItem: + { + if (ActivePose()) + break; + + BPose *pose = fSelectionList->FirstItem(); + if (pose) { + pose->EditFirstWidget(BPoint(0, + fPoseList->IndexOf(pose) * fListElemHeight), this); + } + break; + } + + case kOpenParentDir: + OpenParent(); + break; + + case kCopyAttributes: + if (be_clipboard->Lock()) { + be_clipboard->Clear(); + BMessage *data = be_clipboard->Data(); + if (data != NULL) { + // copy attributes to the clipboard + BMessage state; + SaveState(state); + + BMallocIO stream; + ssize_t size; + if (state.Flatten(&stream, &size) == B_OK) { + data->AddData("application/tracker-columns", B_MIME_TYPE, stream.Buffer(), size); + be_clipboard->Commit(); + } + } + be_clipboard->Unlock(); + } + break; + case kPasteAttributes: + if (be_clipboard->Lock()) { + BMessage *data = be_clipboard->Data(); + if (data != NULL) { + // find the attributes in the clipboard + const void *buffer; + ssize_t size; + if (data->FindData("application/tracker-columns", B_MIME_TYPE, &buffer, &size) == B_OK) { + BMessage state; + if (state.Unflatten((const char *)buffer) == B_OK) { + // remove all current columns (one always stays) + BColumn *old; + while ((old = ColumnAt(0)) != NULL) { + if (!RemoveColumn(old, false)) + break; + } + + // add new columns + for (int32 index = 0; ; index++) { + BColumn *column = BColumn::InstantiateFromMessage(state, index); + if (!column) + break; + AddColumn(column); + } + + // remove the last old one + RemoveColumn(old, false); + + // set sorting mode + BViewState *viewState = BViewState::InstantiateFromMessage(state); + if (viewState != NULL) { + SetPrimarySort(viewState->PrimarySort()); + SetSecondarySort(viewState->SecondarySort()); + SetReverseSort(viewState->ReverseSort()); + + SortPoses(); + Invalidate(); + } + } + } + } + be_clipboard->Unlock(); + } + break; + case kAttributeItem: + HandleAttrMenuItemSelected(message); + break; + + case kAddPrinter: + be_app->PostMessage(message); + break; + + case kMakeActivePrinter: + SetDefaultPrinter(); + break; + +#if DEBUG + case kTestIconCache: + RunIconCacheTests(); + break; + + case 'dbug': + { + int32 count = fSelectionList->CountItems(); + for (int32 index = 0; index < count; index++) + fSelectionList->ItemAt(index)->PrintToStream(); + + break; + } +#ifdef CHECK_OPEN_MODEL_LEAKS + case 'dpfl': + DumpOpenModels(false); + break; + + case 'dpfL': + DumpOpenModels(true); + break; +#endif +#endif + + case kCheckTypeahead: + { + bigtime_t doubleClickSpeed; + get_click_speed(&doubleClickSpeed); + if (system_time() - fLastKeyTime > (doubleClickSpeed * 2)) { + strcpy(fMatchString, ""); + fCountView->SetTypeAhead(fMatchString); + delete fKeyRunner; + fKeyRunner = NULL; + } + break; + } + + case B_OBSERVER_NOTICE_CHANGE: + { + int32 observerWhat; + if (message->FindInt32("be:observe_change_what", &observerWhat) == B_OK) { + switch (observerWhat) { + case kDateFormatChanged: + UpdateDateColumns(message); + break; + + case kVolumesOnDesktopChanged: + AdaptToVolumeChange(message); + break; + + case kDesktopIntegrationChanged: + AdaptToDesktopIntegrationChange(message); + break; + + case kShowSelectionWhenInactiveChanged: + message->FindBool("ShowSelectionWhenInactive", &fShowSelectionWhenInactive); + TrackerSettings().SetShowSelectionWhenInactive(fShowSelectionWhenInactive); + Invalidate(); + break; + + case kTransparentSelectionChanged: + message->FindBool("TransparentSelection", &fTransparentSelection); + TrackerSettings().SetTransparentSelection(fTransparentSelection); + break; + + case kSortFolderNamesFirstChanged: + if (ViewMode() == kListMode) { + bool sortFolderNamesFirst; + message->FindBool("SortFolderNamesFirst", &sortFolderNamesFirst); + TrackerSettings().SetSortFolderNamesFirst(sortFolderNamesFirst); + NameAttributeText::SetSortFolderNamesFirst(sortFolderNamesFirst); + SortPoses(); + Invalidate(); + } + break; + + case kShowVolumeSpaceBar: + bool enabled; + message->FindBool("ShowVolumeSpaceBar", &enabled); + TrackerSettings().SetShowVolumeSpaceBar(enabled); + // supposed to fall through + case kSpaceBarColorChanged: + UpdateVolumeIcons(); + break; + case kUpdateVolumeSpaceBar: + dev_t device; + message->FindInt32("device", (int32 *)&device); + UpdateVolumeIcon(device); + break; + } + } + break; + } + + default: + _inherited::MessageReceived(message); + break; + } +} + + +bool +BPoseView::RemoveColumn(BColumn *columnToRemove, bool runAlert) +{ + // make sure last column is not removed + if (CountColumns() == 1) { + if (runAlert) + (new BAlert("", "You must have at least one Attribute showing.", + "Cancel", 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); + return false; + } + + // column exists so remove it from list + int32 columnIndex = IndexOfColumn(columnToRemove); + float offset = columnToRemove->Offset(); + + int32 count = fPoseList->CountItems(); + for (int32 index = 0; index < count; index++) + fPoseList->ItemAt(index)->RemoveWidget(this, columnToRemove); + fColumnList->RemoveItem(columnToRemove, false); + fTitleView->RemoveTitle(columnToRemove); + + float attrWidth = columnToRemove->Width(); + delete columnToRemove; + + count = CountColumns(); + for (int32 index = columnIndex; index < count; index++) { + BColumn *column = ColumnAt(index); + column->SetOffset(column->Offset() - (attrWidth + kTitleColumnExtraMargin)); + } + + BRect rect(Bounds()); + rect.left = offset; + Invalidate(rect); + + ContainerWindow()->MarkAttributeMenu(); + + if (IsWatchingDateFormatChange()) { + int32 columnCount = CountColumns(); + bool anyDateAttributesLeft = false; + + for (int32 i = 0; iAttrType() == B_TIME_TYPE) + anyDateAttributesLeft = true; + + if (anyDateAttributesLeft) + break; + } + + if (!anyDateAttributesLeft) + StopWatchDateFormatChange(); + } + + fStateNeedsSaving = true; + + return true; +} + + +bool +BPoseView::AddColumn(BColumn *newColumn, const BColumn *after) +{ + if (!after) + after = LastColumn(); + + // add new column after last column + float offset; + int32 afterColumnIndex; + if (after) { + offset = after->Offset() + after->Width() + kTitleColumnExtraMargin; + afterColumnIndex = IndexOfColumn(after); + } else { + offset = kColumnStart; + afterColumnIndex = CountColumns() - 1; + } + + // add the new column + fColumnList->AddItem(newColumn, afterColumnIndex + 1); + fTitleView->AddTitle(newColumn); + + BRect rect(Bounds()); + + // add widget for all visible poses + int32 count = fPoseList->CountItems(); + int32 startIndex = (int32)(rect.top / fListElemHeight); + BPoint loc(0, startIndex * fListElemHeight); + + for (int32 index = startIndex; index < count; index++) { + BPose *pose = fPoseList->ItemAt(index); + if (!pose->WidgetFor(newColumn->AttrHash())) + pose->AddWidget(this, newColumn); + + loc.y += fListElemHeight; + if (loc.y > rect.bottom) + break; + } + + // rearrange column titles to fit new column + newColumn->SetOffset(offset); + float attrWidth = newColumn->Width(); + + count = CountColumns(); + for (int32 index = afterColumnIndex + 2; index < count; index++) { + BColumn *column = ColumnAt(index); + ASSERT(newColumn != column); + column->SetOffset(column->Offset() + (attrWidth + + kTitleColumnExtraMargin)); + } + + rect.left = offset; + Invalidate(rect); + ContainerWindow()->MarkAttributeMenu(); + + // Check if this is a time attribute and if so, + // start watching for changed in time/date format: + if (!IsWatchingDateFormatChange() && newColumn->AttrType() == B_TIME_TYPE) + StartWatchDateFormatChange(); + + fStateNeedsSaving = true; + + return true; +} + + +void +BPoseView::HandleAttrMenuItemSelected(BMessage *message) +{ + // see if source was a menu item + BMenuItem *item; + if (message->FindPointer("source", (void **)&item) != B_OK) + item = NULL; + + // find out which column was selected + uint32 attrHash; + if (message->FindInt32("attr_hash", (int32 *)&attrHash) != B_OK) + return; + + BColumn *column = ColumnFor(attrHash); + if (column) { + RemoveColumn(column, true); + return; + } else { + // collect info about selected attribute + const char *attrName; + if (message->FindString("attr_name", &attrName) != B_OK) + return; + + uint32 attrType; + if (message->FindInt32("attr_type", (int32 *)&attrType) != B_OK) + return; + + float attrWidth; + if (message->FindFloat("attr_width", &attrWidth) != B_OK) + return; + + alignment attrAlign; + if (message->FindInt32("attr_align", (int32 *)&attrAlign) != B_OK) + return; + + bool isEditable; + if (message->FindBool("attr_editable", &isEditable) != B_OK) + return; + + bool isStatfield; + if (message->FindBool("attr_statfield", &isStatfield) != B_OK) + return; + + column = new BColumn(item->Label(), 0, attrWidth, attrAlign, + attrName, attrType, isStatfield, isEditable); + AddColumn(column); + if (item->Menu()->Supermenu() == NULL) + delete item->Menu(); + } +} + + +const int32 kSanePoseLocation = 50000; + + +void +BPoseView::ReadPoseInfo(Model *model, PoseInfo *poseInfo) +{ + BModelOpener opener(model); + if (!model->Node()) + return; + + ReadAttrResult result = kReadAttrFailed; + + // special case the "root" disks icon + if (model->IsRoot()) { + BVolume bootVol; + BDirectory dir; + + BVolumeRoster().GetBootVolume(&bootVol); + if (FSGetDeskDir(&dir, bootVol.Device()) == B_OK) { + result = ReadAttr(&dir, kAttrDisksPoseInfo, kAttrDisksPoseInfoForeign, + B_RAW_TYPE, 0, poseInfo, sizeof(*poseInfo), &PoseInfo::EndianSwap); + } + } else { + ASSERT(model->IsNodeOpen()); + for (int32 count = 10; count >= 0; count--) { + if (!model->Node()) + break; + + result = ReadAttr(model->Node(), kAttrPoseInfo, kAttrPoseInfoForeign, + B_RAW_TYPE, 0, poseInfo, sizeof(*poseInfo), &PoseInfo::EndianSwap); + + if (result != kReadAttrFailed) { + // got it, bail + break; + } + + // if we're in one of the icon modes and it's a newly created item + // then we're going to retry a few times to see if we can get some + // pose info to properly place the icon + if (ViewMode() == kListMode) + break; + + const StatStruct *stat = model->StatBuf(); + if (stat->st_crtime != stat->st_mtime) + break; + + // PRINT(("retrying to read pose info for %s, %d\n", model->Name(), count)); + + snooze(10000); + } + } + if (result == kReadAttrFailed) { + poseInfo->fInitedDirectory = -1LL; + poseInfo->fInvisible = false; + } else if (!TargetModel() + || (poseInfo->fInitedDirectory != model->EntryRef()->directory + && (poseInfo->fInitedDirectory != TargetModel()->NodeRef()->node))) { + // info was read properly but it's not for this directory + poseInfo->fInitedDirectory = -1LL; + } else if (poseInfo->fLocation.x < -kSanePoseLocation + || poseInfo->fLocation.x > kSanePoseLocation + || poseInfo->fLocation.y < -kSanePoseLocation + || poseInfo->fLocation.y > kSanePoseLocation) { + // location values not realistic, probably screwed up, force reset + poseInfo->fInitedDirectory = -1LL; + } +} + + +ExtendedPoseInfo * +BPoseView::ReadExtendedPoseInfo(Model *model) +{ + BModelOpener opener(model); + if (!model->Node()) + return NULL; + + ReadAttrResult result = kReadAttrFailed; + + const char *extendedPoseInfoAttrName; + const char *extendedPoseInfoAttrForeignName; + + // special case the "root" disks icon + if (model->IsRoot()) { + BVolume bootVol; + BDirectory dir; + + BVolumeRoster().GetBootVolume(&bootVol); + if (FSGetDeskDir(&dir, bootVol.Device()) == B_OK) { + extendedPoseInfoAttrName = kAttrExtendedDisksPoseInfo; + extendedPoseInfoAttrForeignName = kAttrExtendedDisksPoseInfoForegin; + } else + return NULL; + } else { + extendedPoseInfoAttrName = kAttrExtendedPoseInfo; + extendedPoseInfoAttrForeignName = kAttrExtendedPoseInfoForegin; + } + + type_code type; + size_t size; + result = GetAttrInfo(model->Node(), extendedPoseInfoAttrName, + extendedPoseInfoAttrForeignName, &type, &size); + + if (result == kReadAttrFailed) + return NULL; + + char *buffer = new char[ExtendedPoseInfo::SizeWithHeadroom(size)]; + ExtendedPoseInfo *poseInfo = reinterpret_cast(buffer); + + result = ReadAttr(model->Node(), extendedPoseInfoAttrName, + extendedPoseInfoAttrForeignName, + B_RAW_TYPE, 0, buffer, size, &ExtendedPoseInfo::EndianSwap); + + // check that read worked, and data is sane + if (result == kReadAttrFailed + || size > poseInfo->SizeWithHeadroom() + || size < poseInfo->Size()) { + delete [] buffer; + return NULL; + } + + return poseInfo; +} + + +void +BPoseView::SetViewMode(uint32 newMode) +{ + if (newMode == ViewMode()) + return; + + ASSERT(!IsFilePanel()); + + uint32 lastIconMode = fViewState->LastIconMode(); + if (newMode != kListMode) + fViewState->SetLastIconMode(newMode); + + uint32 oldMode = ViewMode(); + fViewState->SetViewMode(newMode); + + BContainerWindow *window = ContainerWindow(); + if (oldMode == kListMode) { + fTitleView->RemoveSelf(); + + if (window) + window->HideAttributeMenu(); + + MoveBy(0, -(kTitleViewHeight + 1)); + ResizeBy(0, kTitleViewHeight + 1); + } else if (ViewMode() == kListMode) { + MoveBy(0, kTitleViewHeight + 1); + ResizeBy(0, -(kTitleViewHeight + 1)); + + if (window) + window->ShowAttributeMenu(); + + fTitleView->ResizeTo(Frame().Width(), fTitleView->Frame().Height()); + fTitleView->MoveTo(Frame().left, Frame().top - (kTitleViewHeight + 1)); + if (Parent()) + Parent()->AddChild(fTitleView); + else + Window()->AddChild(fTitleView); + } + + CommitActivePose(); + SetIconPoseHeight(); + GetLayoutInfo(ViewMode(), &fGrid, &fOffset); + + // see if we need to map icons into new mode + bool mapIcons; + if (fOkToMapIcons) + mapIcons = (ViewMode() != kListMode) && (ViewMode() != lastIconMode); + else + mapIcons = false; + + BPoint oldOffset; + BPoint oldGrid; + if (mapIcons) + GetLayoutInfo(lastIconMode, &oldGrid, &oldOffset); + + BRect bounds(Bounds()); + PoseList newPoseList(30); + + if (ViewMode() != kListMode) { + int32 count = fPoseList->CountItems(); + for (int32 index = 0; index < count; index++) { + BPose *pose = fPoseList->ItemAt(index); + if (pose->HasLocation() == false) + newPoseList.AddItem(pose); + else if (mapIcons) + MapToNewIconMode(pose, oldGrid, oldOffset); + } + } + + // save the current origin and get origin for new view mode + BPoint origin(LeftTop()); + BPoint newOrigin(origin); + + if (ViewMode() == kListMode) { + newOrigin = fViewState->ListOrigin(); + fViewState->SetIconOrigin(origin); + } else if (oldMode == kListMode) { + fViewState->SetListOrigin(origin); + newOrigin = fViewState->IconOrigin(); + } + + PinPointToValidRange(newOrigin); + + DisableScrollBars(); + ScrollTo(newOrigin); + + // reset hint and arrange poses which DO NOT have a location yet + ResetPosePlacementHint(); + int32 count = newPoseList.CountItems(); + for (int32 index = 0; index < count; index++) { + BPose *pose = newPoseList.ItemAt(index); + PlacePose(pose, bounds); + AddToVSList(pose); + } + + // sort poselist if we are switching to list mode + if (ViewMode() == kListMode) + SortPoses(); + else + RecalcExtent(); + + UpdateScrollRange(); + SetScrollBarsTo(newOrigin); + EnableScrollBars(); + ContainerWindow()->ViewModeChanged(oldMode, newMode); + Invalidate(); +} + + +void +BPoseView::MapToNewIconMode(BPose *pose, BPoint oldGrid, BPoint oldOffset) +{ + BPoint delta; + BPoint poseLoc; + + poseLoc = PinToGrid(pose->Location(), oldGrid, oldOffset); + delta = pose->Location() - poseLoc; + poseLoc -= oldOffset; + + if (poseLoc.x >= 0) + poseLoc.x = floorf(poseLoc.x / oldGrid.x) * fGrid.x; + else + poseLoc.x = ceilf(poseLoc.x / oldGrid.x) * fGrid.x; + + if (poseLoc.y >= 0) + poseLoc.y = floorf(poseLoc.y / oldGrid.y) * fGrid.y; + else + poseLoc.y = ceilf(poseLoc.y / oldGrid.y) * fGrid.y; + + if ((delta.x != 0) || (delta.y != 0)) { + if (delta.x >= 0) + delta.x = fGrid.x * floorf(delta.x / oldGrid.x); + else + delta.x = fGrid.x * ceilf(delta.x / oldGrid.x); + + if (delta.y >= 0) + delta.y = fGrid.y * floorf(delta.y / oldGrid.y); + else + delta.y = fGrid.y * ceilf(delta.y / oldGrid.y); + + poseLoc += delta; + } + + poseLoc += fOffset; + pose->SetLocation(poseLoc); + pose->SetSaveLocation(); +} + + +inline bool +BPoseView::HasPosesInClipboard() +{ + return fHasPosesInClipboard; +} + + +inline void +BPoseView::SetHasPosesInClipboard(bool hasPoses) +{ + fHasPosesInClipboard = hasPoses; +} + + +void +BPoseView::SetPosesClipboardMode(uint32 clipboardMode) +{ + int32 count = fPoseList->CountItems(); + if (ViewMode() == kListMode) { + BPoint loc(0,0); + for (int32 index = 0; index < count; index++) { + BPose *pose = fPoseList->ItemAt(index); + if (pose->ClipboardMode() != clipboardMode) { + pose->SetClipboardMode(clipboardMode); + Invalidate(pose->CalcRect(loc, this, false)); + } + loc.y += fListElemHeight; + } + } else { + for (int32 index = 0; index < count; index++) { + BPose *pose = fPoseList->ItemAt(index); + if (pose->ClipboardMode() != clipboardMode) { + pose->SetClipboardMode(clipboardMode); + BRect poseRect(pose->CalcRect(this)); + Invalidate(poseRect); + } + } + } +} + + +void +BPoseView::UpdatePosesClipboardModeFromClipboard(BMessage *clipboardReport) +{ + CommitActivePose(); + fSelectionPivotPose = NULL; + fRealPivotPose = NULL; + bool fullInvalidateNeeded = false; + + node_ref node; + clipboardReport->FindInt32("device", &node.device); + clipboardReport->FindInt64("directory", &node.node); + + bool clearClipboard = false; + clipboardReport->FindBool("clearClipboard", &clearClipboard); + + if (clearClipboard && fHasPosesInClipboard) { + // clear all poses + int32 count = fPoseList->CountItems(); + for (int32 index = 0; index < count; index++) { + BPose *pose = fPoseList->ItemAt(index); + pose->Select(false); + pose->SetClipboardMode(0); + } + SetHasPosesInClipboard(false); + fullInvalidateNeeded = true; + fHasPosesInClipboard = false; + } + + BRect bounds(Bounds()); + BPoint loc(0, 0); + bool hasPosesInClipboard = false; + int32 foundNodeIndex = 0; + + TClipboardNodeRef *clipNode = NULL; + ssize_t size; + for (int32 index = 0; clipboardReport->FindData("tcnode", T_CLIPBOARD_NODE, index, + (const void **)&clipNode, &size) == B_OK; index++) { + BPose *pose = fPoseList->FindPose(&clipNode->node, &foundNodeIndex); + if (pose == NULL) + continue; + + if (clipNode->moveMode != pose->ClipboardMode() || pose->IsSelected()) { + pose->SetClipboardMode(clipNode->moveMode); + pose->Select(false); + + if (!fullInvalidateNeeded) { + if (ViewMode() == kListMode) { + loc.y = foundNodeIndex * fListElemHeight; + if (loc.y <= bounds.bottom && loc.y >= bounds.top) + Invalidate(pose->CalcRect(loc, this, false)); + } else { + BRect poseRect(pose->CalcRect(this)); + if (bounds.Contains(poseRect.LeftTop()) + || bounds.Contains(poseRect.LeftBottom()) + || bounds.Contains(poseRect.RightBottom()) + || bounds.Contains(poseRect.RightTop())) { + if (!EraseWidgetTextBackground() + || clipNode->moveMode == kMoveSelectionTo) + Invalidate(poseRect); + else + pose->Draw(poseRect, this, false); + } + } + } + if (clipNode->moveMode) + hasPosesInClipboard = true; + } + } + + fSelectionList->MakeEmpty(); + fMimeTypesInSelectionCache.MakeEmpty(); + + SetHasPosesInClipboard(hasPosesInClipboard | fHasPosesInClipboard); + + if (fullInvalidateNeeded) + Invalidate(); +} + + +void +BPoseView::PlaceFolder(const entry_ref *ref, const BMessage *message) +{ + BNode node(ref); + BPoint location; + bool setPosition = false; + + if (message->FindPoint("be:invoke_origin", &location) == B_OK) { + // new folder created from popup, place on click point + setPosition = true; + location = ConvertFromScreen(location); + } else if (ViewMode() != kListMode) { + // new folder created by keyboard shortcut + uint32 buttons; + GetMouse(&location, &buttons); + BPoint globalLocation(location); + ConvertToScreen(&globalLocation); + // check if mouse over window + if (Window()->Frame().Contains(globalLocation)) + // create folder under mouse + setPosition = true; + } + + if (setPosition) + FSSetPoseLocation(TargetModel()->NodeRef()->node, &node, + location); +} + + +void +BPoseView::NewFileFromTemplate(const BMessage *message) +{ + ASSERT(TargetModel()); + + entry_ref destEntryRef; + node_ref destNodeRef; + + BDirectory destDir(TargetModel()->NodeRef()); + if (destDir.InitCheck() != B_OK) + return; + + char fileName[B_FILE_NAME_LENGTH] = "New "; + strcat(fileName, message->FindString("name")); + FSMakeOriginalName(fileName, &destDir, " copy"); + + entry_ref srcRef; + message->FindRef("refs_template", &srcRef); + + BDirectory dir(&srcRef); + + if (dir.InitCheck() == B_OK) { + // special handling of directories + if (FSCreateNewFolderIn(TargetModel()->NodeRef(), &destEntryRef, &destNodeRef) == B_OK) { + BEntry destEntry(&destEntryRef); + destEntry.Rename(fileName); + } + } else { + BFile srcFile(&srcRef, B_READ_ONLY); + BFile destFile(&destDir, fileName, B_READ_WRITE | B_CREATE_FILE); + + // copy the data from the template file + char *buffer = new char[1024]; + ssize_t result; + do { + result = srcFile.Read(buffer, 1024); + + if (result > 0) { + ssize_t written = destFile.Write(buffer, (size_t)result); + if (written != result) + result = written < B_OK ? written : B_ERROR; + } + } while (result > 0); + delete[] buffer; + } + + // todo: create an UndoItem + + // copy the attributes from the template file + BNode srcNode(&srcRef); + BNode destNode(&destDir, fileName); + FSCopyAttributesAndStats(&srcNode, &destNode); + + BEntry entry(&destDir, fileName); + entry.GetRef(&destEntryRef); + + // try to place new item at click point or under mouse if possible + PlaceFolder(&destEntryRef, message); + + if (dir.InitCheck() == B_OK) { + // special-case directories - start renaming them + int32 index; + BPose *pose = EntryCreated(TargetModel()->NodeRef(), &destNodeRef, + destEntryRef.name, &index); + + if (pose) { + UpdateScrollRange(); + CommitActivePose(); + SelectPose(pose, index); + pose->EditFirstWidget(BPoint(0, index * fListElemHeight), this); + } + } else { + // open the corresponding application + BMessage openMessage(B_REFS_RECEIVED); + openMessage.AddRef("refs", &destEntryRef); + + // add a messenger to the launch message that will be used to + // dispatch scripting calls from apps to the PoseView + openMessage.AddMessenger("TrackerViewToken", BMessenger(this)); + + if (fSelectionHandler) + fSelectionHandler->PostMessage(&openMessage); + } +} + + +void +BPoseView::NewFolder(const BMessage *message) +{ + ASSERT(TargetModel()); + + entry_ref ref; + node_ref nodeRef; + + if (FSCreateNewFolderIn(TargetModel()->NodeRef(), &ref, &nodeRef) == B_OK) { + // try to place new folder at click point or under mouse if possible + + PlaceFolder(&ref, message); + + int32 index; + BPose *pose = EntryCreated(TargetModel()->NodeRef(), &nodeRef, ref.name, &index); + if (pose) { + UpdateScrollRange(); + CommitActivePose(); + SelectPose(pose, index); + pose->EditFirstWidget(BPoint(0, index * fListElemHeight), this); + } + } +} + + +void +BPoseView::Cleanup(bool doAll) +{ + if (ViewMode() == kListMode) + return; + + BContainerWindow *window = ContainerWindow(); + if (!window) + return; + + // replace all icons from the top + if (doAll) { + // sort by sort field + SortPoses(); + + DisableScrollBars(); + ClearExtent(); + ClearSelection(); + ScrollTo(B_ORIGIN); + UpdateScrollRange(); + SetScrollBarsTo(B_ORIGIN); + ResetPosePlacementHint(); + + BRect viewBounds(Bounds()); + + // relocate all poses in list (reset vs list) + fVSPoseList->MakeEmpty(); + int32 count = fPoseList->CountItems(); + for (int32 index = 0; index < count; index++) { + BPose *pose = fPoseList->ItemAt(index); + PlacePose(pose, viewBounds); + AddToVSList(pose); + } + + RecalcExtent(); + + // scroll icons into view so that leftmost icon is "fOffset" from left + UpdateScrollRange(); + EnableScrollBars(); + + if (HScrollBar()) { + float min; + float max; + HScrollBar()->GetRange(&min, &max); + HScrollBar()->SetValue(min); + } + + UpdateScrollRange(); + Invalidate(viewBounds); + + } else { + // clean up items to nearest locations + BRect viewBounds(Bounds()); + int32 count = fPoseList->CountItems(); + for (int32 index = 0; index < count; index++) { + BPose *pose = fPoseList->ItemAt(index); + BPoint location(pose->Location()); + BPoint newLocation(PinToGrid(location, fGrid, fOffset)); + + // do we need to move pose to a grid location? + if (newLocation != location) { + // remove pose from VSlist so it doesn't "bump" into itself + RemoveFromVSList(pose); + + // try new grid location + BRect oldBounds(pose->CalcRect(this)); + BRect poseBounds(oldBounds); + pose->MoveTo(newLocation, this); + if (SlotOccupied(oldBounds, viewBounds)) { + ResetPosePlacementHint(); + PlacePose(pose, viewBounds); + poseBounds = pose->CalcRect(this); + } + + AddToVSList(pose); + AddToExtent(poseBounds); + + if (viewBounds.Intersects(poseBounds)) + Invalidate(poseBounds); + if (viewBounds.Intersects(oldBounds)) + Invalidate(oldBounds); + } + } + } +} + + +void +BPoseView::PlacePose(BPose *pose, BRect &viewBounds) +{ + // move pose to probable location + pose->SetLocation(fHintLocation); + BRect rect(pose->CalcRect(this)); + BPoint deltaFromBounds(fHintLocation - rect.LeftTop()); + + // make pose rect a little bigger to ensure space between poses + rect.InsetBy(-3, 0); + + BRect deskbarFrame; + bool checkDeskbarFrame = false; + if (IsDesktopWindow() && get_deskbar_frame(&deskbarFrame) == B_OK) { + checkDeskbarFrame = true; + deskbarFrame.InsetBy(-10, -10); + } + + // find an empty slot to put pose into + if (fVSPoseList->CountItems() > 0) + while (SlotOccupied(rect, viewBounds) + // avoid Deskbar + || (checkDeskbarFrame && deskbarFrame.Intersects(rect))) + NextSlot(pose, rect, viewBounds); + + rect.InsetBy(3, 0); + + fHintLocation = pose->Location() + BPoint(fGrid.x, 0); + + pose->SetLocation(rect.LeftTop() + deltaFromBounds); + pose->SetSaveLocation(); +} + + +void +BPoseView::CheckAutoPlacedPoses() +{ + BRect viewBounds(Bounds()); + + int32 count = fPoseList->CountItems(); + for (int32 index = 0; index < count; index++) { + BPose *pose = fPoseList->ItemAt(index); + if (pose->WasAutoPlaced()) { + RemoveFromVSList(pose); + fHintLocation = pose->Location(); + BRect oldBounds(pose->CalcRect(this)); + PlacePose(pose, viewBounds); + + BRect newBounds(pose->CalcRect(this)); + AddToVSList(pose); + pose->SetAutoPlaced(false); + AddToExtent(newBounds); + + Invalidate(oldBounds); + Invalidate(newBounds); + } + } +} + + +void +BPoseView::CheckPoseVisibility(BRect *newFrame) +{ + bool desktop = IsDesktopWindow() && newFrame != 0; + + BRect deskFrame; + if (desktop) { + ASSERT(newFrame); + deskFrame = *newFrame; + } + + ASSERT(ViewMode() != kListMode); + + BRect bounds(Bounds()); + bounds.InsetBy(20, 20); + + int32 count = fPoseList->CountItems(); + for (int32 index = 0; index < count; index++) { + BPose *pose = fPoseList->ItemAt(index); + BPoint newLocation(pose->Location()); + bool locationNeedsUpdating = false; + + if (desktop) { + // we just switched screen resolution, pick up the right + // icon locations for the new resolution + Model *model = pose->TargetModel(); + ExtendedPoseInfo *info = ReadExtendedPoseInfo(model); + if (info && info->HasLocationForFrame(deskFrame)) { + BPoint locationForFrame = info->LocationForFrame(deskFrame); + if (locationForFrame != newLocation) { + // found one and it is different from the current + newLocation = locationForFrame; + locationNeedsUpdating = true; + Invalidate(pose->CalcRect(this)); + // make sure the old icon gets erased + RemoveFromVSList(pose); + pose->SetLocation(newLocation); + // set the new location + } + } + delete [] (char *)info; + // ToDo: + // fix up this mess + } + + BRect rect(pose->CalcRect(this)); + if (!rect.Intersects(bounds)) { + // pose doesn't fit on screen + if (!locationNeedsUpdating) { + // didn't already invalidate and remove in the desktop case + Invalidate(rect); + RemoveFromVSList(pose); + } + BPoint loc(pose->Location()); + loc.ConstrainTo(bounds); + // place it onscreen + + pose->SetLocation(loc); + // set the new location + locationNeedsUpdating = true; + } + + if (locationNeedsUpdating) { + // pose got reposition by one or both of the above + pose->SetSaveLocation(); + AddToVSList(pose); + // add it at the new location + Invalidate(pose->CalcRect(this)); + // make sure the new pose location updates properly + } + } +} + + +bool +BPoseView::SlotOccupied(BRect poseRect, BRect viewBounds) const +{ + if (fVSPoseList->IsEmpty()) + return false; + + // ## be sure to keep this code in sync with calls to NextSlot + // ## in terms of the comparison of fHintLocation and PinToGrid + if (poseRect.right >= viewBounds.right) { + BPoint point(viewBounds.left + fOffset.x, 0); + point = PinToGrid(point, fGrid, fOffset); + if (fHintLocation.x != point.x) + return true; + } + + // search only nearby poses (vertically) + int32 index = FirstIndexAtOrBelow((int32)(poseRect.top - IconPoseHeight())); + int32 numPoses = fVSPoseList->CountItems(); + + while (index < numPoses && fVSPoseList->ItemAt(index)->Location().y + < poseRect.bottom) { + + BRect rect(fVSPoseList->ItemAt(index)->CalcRect(this)); + if (poseRect.Intersects(rect)) + return true; + + index++; + } + + return false; +} + + +void +BPoseView::NextSlot(BPose *pose, BRect &poseRect, BRect viewBounds) +{ + // move to next slot + poseRect.OffsetBy(fGrid.x, 0); + + // if we reached the end of row go down to next row + if (poseRect.right > viewBounds.right) { + fHintLocation.y += fGrid.y; + fHintLocation.x = viewBounds.left + fOffset.x; + fHintLocation = PinToGrid(fHintLocation, fGrid, fOffset); + pose->SetLocation(fHintLocation); + poseRect = pose->CalcRect(this); + poseRect.InsetBy(-3, 0); + } +} + + +int32 +BPoseView::FirstIndexAtOrBelow(int32 y, bool constrainIndex) const +{ +// This method performs a binary search on the vertically sorted pose list +// and returns either the index of the first pose at a given y location or +// the proper index to insert a new pose into the list. + + int32 index = 0; + int32 l = 0; + int32 r = fVSPoseList->CountItems() - 1; + + while (l <= r) { + index = (l + r) >> 1; + int32 result = (int32)(y - fVSPoseList->ItemAt(index)->Location().y); + + if (result < 0) + r = index - 1; + else if (result > 0) + l = index + 1; + else { + // compare turned out equal, find first pose + while (index > 0 + && y == fVSPoseList->ItemAt(index - 1)->Location().y) + index--; + return index; + } + } + + // didn't find pose AT location y - bump index to proper insert point + while (index < fVSPoseList->CountItems() + && fVSPoseList->ItemAt(index)->Location().y <= y) + index++; + + // if flag is true then constrain index to legal value since this + // method returns the proper insertion point which could be outside + // the current bounds of the list + if (constrainIndex && index >= fVSPoseList->CountItems()) + index = fVSPoseList->CountItems() - 1; + + return index; +} + + +void +BPoseView::AddToVSList(BPose *pose) +{ + int32 index = FirstIndexAtOrBelow((int32)pose->Location().y, false); + fVSPoseList->AddItem(pose, index); +} + + +int32 +BPoseView::RemoveFromVSList(const BPose *pose) +{ + int32 index = FirstIndexAtOrBelow((int32)pose->Location().y); + + int32 count = fVSPoseList->CountItems(); + for (; index < count; index++) { + BPose *matchingPose = fVSPoseList->ItemAt(index); + ASSERT(matchingPose); + if (!matchingPose) + return -1; + + if (pose == matchingPose) { + fVSPoseList->RemoveItemAt(index); + return index; + } + } + + return -1; +} + + +BPoint +BPoseView::PinToGrid(BPoint point, BPoint grid, BPoint offset) const +{ + if (grid.x == 0 || grid.y == 0) + return point; + + point -= offset; + BPoint gridLoc(point); + + if (point.x >= 0) + gridLoc.x = floorf((point.x / grid.x) + 0.5f) * grid.x; + else + gridLoc.x = ceilf((point.x / grid.x) - 0.5f) * grid.x; + + if (point.y >= 0) + gridLoc.y = floorf((point.y / grid.y) + 0.5f) * grid.y; + else + gridLoc.y = ceilf((point.y / grid.y) - 0.5f) * grid.y; + + gridLoc += offset; + return gridLoc; +} + + +void +BPoseView::ResetPosePlacementHint() +{ + fHintLocation = PinToGrid(BPoint(LeftTop().x + fOffset.x, + LeftTop().y + fOffset.y), fGrid, fOffset); +} + + +void +BPoseView::SelectPoses(int32 start, int32 end) +{ + BPoint loc(0, 0); + BRect bounds(Bounds()); + + // clear selection list + fSelectionList->MakeEmpty(); + fMimeTypesInSelectionCache.MakeEmpty(); + fSelectionPivotPose = NULL; + fRealPivotPose = NULL; + + bool iconMode = ViewMode() != kListMode; + + int32 count = fPoseList->CountItems(); + for (int32 index = start; index < end && index < count; index++) { + BPose *pose = fPoseList->ItemAt(index); + fSelectionList->AddItem(pose); + if (index == start) + fSelectionPivotPose = pose; + if (!pose->IsSelected()) { + pose->Select(true); + BRect poseRect; + if (iconMode) + poseRect = pose->CalcRect(this); + else + poseRect = pose->CalcRect(loc, this); + + if (bounds.Intersects(poseRect)) { + if (EraseWidgetTextBackground()) + Invalidate(poseRect); + else + pose->Draw(poseRect, this, false); + Flush(); + } + } + + loc.y += fListElemHeight; + } +} + + +void +BPoseView::ScrollIntoView(BPose *pose, int32 index, bool drawOnly) +{ + BRect poseRect; + + if (ViewMode() == kListMode) + poseRect = CalcPoseRect(pose, index); + else + poseRect = pose->CalcRect(this); + + if (!IsDesktopWindow() && !drawOnly) { + BRect testRect(poseRect); + + if (ViewMode() == kListMode) { + // if we're in list view then we only care that the entire + // pose is visible vertically, not horizontally + testRect.left = 0; + testRect.right = testRect.left + 1; + } + if (!Bounds().Contains(testRect)) + SetScrollBarsTo(testRect.LeftTop()); + } + + if (Bounds().Intersects(poseRect)) + pose->Draw(poseRect, this, false); +} + + +void +BPoseView::SelectPose(BPose *pose, int32 index, bool scrollIntoView) +{ + if (!pose || fSelectionList->CountItems() > 1 || !pose->IsSelected()) + ClearSelection(); + + AddPoseToSelection(pose, index, scrollIntoView); +} + + +void +BPoseView::AddPoseToSelection(BPose *pose, int32 index, bool scrollIntoView) +{ + // ToDo: + // need to check if pose is member of selection list + if (pose && !pose->IsSelected()) { + pose->Select(true); + fSelectionList->AddItem(pose); + + ScrollIntoView(pose, index, !scrollIntoView); + + if (fSelectionChangedHook) + ContainerWindow()->SelectionChanged(); + } +} + + +void +BPoseView::RemovePoseFromSelection(BPose *pose) +{ + if (fSelectionPivotPose == pose) + fSelectionPivotPose = NULL; + if (fRealPivotPose == pose) + fRealPivotPose = NULL; + + if (!fSelectionList->RemoveItem(pose)) + // wasn't selected to begin with + return; + + pose->Select(false); + if (ViewMode() == kListMode) { + // ToDo: + // need a simple call to CalcRect that works both in listView and icon view modes + // without the need for an index/pos + int32 count = fPoseList->CountItems(); + BPoint loc(0, 0); + for (int32 index = 0; index < count; index++) { + if (pose == fPoseList->ItemAt(index)) { + Invalidate(pose->CalcRect(loc, this)); + break; + } + loc.y += fListElemHeight; + } + } else + Invalidate(pose->CalcRect(this)); + + if (fSelectionChangedHook) + ContainerWindow()->SelectionChanged(); +} + + +bool +BPoseView::EachItemInDraggedSelection(const BMessage *message, + bool (*func)(BPose *, BPoseView *, void *), BPoseView *poseView, void *passThru) +{ + BContainerWindow *srcWindow; + message->FindPointer("src_window", (void **)&srcWindow); + + AutoLock lock(srcWindow); + if (!lock) + return false; + + PoseList *selectionList = srcWindow->PoseView()->SelectionList(); + int32 count = selectionList->CountItems(); + + for (int32 index = 0; index < count; index++) { + BPose *pose = selectionList->ItemAt(index); + if (func(pose, poseView, passThru)) + // early iteration termination + return true; + } + return false; +} + + +static bool +ContainsOne(BString *string, const char *matchString) +{ + return strcmp(string->String(), matchString) == 0; +} + + +bool +BPoseView::FindDragNDropAction(const BMessage *dragMessage, bool &canCopy, + bool &canMove, bool &canLink, bool &canErase) +{ + canCopy = false; + canMove = false; + canErase = false; + canLink = false; + if (!dragMessage->HasInt32("be:actions")) + return false; + + int32 action; + for (int32 index = 0; + dragMessage->FindInt32("be:actions", index, &action) == B_OK; index++) { + switch (action) { + case B_MOVE_TARGET: + canMove = true; + break; + + case B_COPY_TARGET: + canCopy = true; + break; + + case B_TRASH_TARGET: + canErase = true; + break; + + case B_LINK_TARGET: + canLink = true; + break; + } + } + return canCopy || canMove || canErase || canLink; +} + + +bool +BPoseView::CanTrashForeignDrag(const Model *targetModel) +{ + BEntry entry(targetModel->EntryRef()); + return FSIsTrashDir(&entry); +} + + +bool +BPoseView::CanCopyOrMoveForeignDrag(const Model *targetModel, + const BMessage *dragMessage) +{ + if (!targetModel->IsDirectory()) + return false; + + // in order to handle a clipping file, the drag initiator must be able + // do deal with B_FILE_MIME_TYPE + for (int32 index = 0; ; index++) { + const char *type; + if (dragMessage->FindString("be:types", index, &type) != B_OK) + break; + + if (strcasecmp(type, B_FILE_MIME_TYPE) == 0) + return true; + } + + return false; +} + + +bool +BPoseView::CanHandleDragSelection(const Model *target, const BMessage *dragMessage, + bool ignoreTypes) +{ + if (ignoreTypes) + return target->IsDropTarget(); + + ASSERT(dragMessage); + + BContainerWindow *srcWindow; + dragMessage->FindPointer("src_window", (void **)&srcWindow); + if (!srcWindow) { + // handle a foreign drag + bool canCopy; + bool canMove; + bool canErase; + bool canLink; + FindDragNDropAction(dragMessage, canCopy, canMove, canLink, canErase); + if (canErase && CanTrashForeignDrag(target)) + return true; + + if (canCopy || canMove) { + if (CanCopyOrMoveForeignDrag(target, dragMessage)) + return true; + + // ToDo: + // collect all mime types here and pass into + // target->IsDropTargetForList(mimeTypeList); + } + + // handle an old style entry_refs only darg message + if (dragMessage->HasRef("refs") && target->IsDirectory()) + return true; + + // handle simple text clipping drag&drop message + if (dragMessage->HasData(kPlainTextMimeType, B_MIME_TYPE) && target->IsDirectory()) + return true; + + // handle simple bitmap clipping drag&drop message + if (target->IsDirectory() + && (dragMessage->HasData(kBitmapMimeType, B_MESSAGE_TYPE) + || dragMessage->HasData(kLargeIconType, B_MESSAGE_TYPE) + || dragMessage->HasData(kMiniIconType, B_MESSAGE_TYPE))) + return true; + + // ToDo: + // check for a drag message full of refs, feed a list of their types to + // target->IsDropTargetForList(mimeTypeList); + return false; + } + + AutoLock lock(srcWindow); + if (!lock) + return false; + BObjectList *mimeTypeList = srcWindow->PoseView()->MimeTypesInSelection(); + if (mimeTypeList->IsEmpty()) { + PoseList *selectionList = srcWindow->PoseView()->SelectionList(); + if (!selectionList->IsEmpty()) { + // no cached data yet, build the cache + int32 count = selectionList->CountItems(); + + for (int32 index = 0; index < count; index++) { + // get the mime type of the model, following a possible symlink + BEntry entry(selectionList->ItemAt(index)->TargetModel()->EntryRef(), true); + if (entry.InitCheck() != B_OK) + continue; + + BFile file(&entry, O_RDONLY); + BNodeInfo mime(&file); + + if (mime.InitCheck() != B_OK) + continue; + + char mimeType[B_MIME_TYPE_LENGTH]; + mime.GetType(mimeType); + + // add unique type string + if (!WhileEachListItem(mimeTypeList, ContainsOne, (const char *)mimeType)) { + BString *newMimeString = new BString(mimeType); + mimeTypeList->AddItem(newMimeString); + } + } + } + } + + return target->IsDropTargetForList(mimeTypeList); +} + + +void +BPoseView::TrySettingPoseLocation(BNode *node, BPoint point) +{ + if (ViewMode() == kListMode) + return; + + if (modifiers() & B_COMMAND_KEY) + // allign to grid if needed + point = PinToGrid(point, fGrid, fOffset); + + if (FSSetPoseLocation(TargetModel()->NodeRef()->node, node, point) == B_OK) + // get rid of opposite endianness attribute + node->RemoveAttr(kAttrPoseInfoForeign); +} + + +status_t +BPoseView::CreateClippingFile(BPoseView *poseView, BFile &result, char *resultingName, + BDirectory *dir, BMessage *message, const char *fallbackName, + bool setLocation, BPoint dropPoint) +{ + // build a file name + // try picking it up from the message + const char *suggestedName; + if (message && message->FindString("be:clip_name", &suggestedName) == B_OK) + strncpy(resultingName, suggestedName, B_FILE_NAME_LENGTH - 1); + else + strcpy(resultingName, fallbackName); + + FSMakeOriginalName(resultingName, dir, ""); + + // create a clipping file + status_t error = dir->CreateFile(resultingName, &result, true); + if (error != B_OK) + return error; + + if (setLocation && poseView) + poseView->TrySettingPoseLocation(&result, dropPoint); + + return B_OK; +} + + +static int32 +RunMimeTypeDestinationMenu(const char *actionText, const BObjectList *types, + const BObjectList *specificItems, BPoint where) +{ + int32 count; + + if (types) + count = types->CountItems(); + else + count = specificItems->CountItems(); + + if (!count) + return 0; + + BPopUpMenu *menu = new BPopUpMenu("create clipping"); + menu->SetFont(be_plain_font); + + for (int32 index = 0; index < count; index++) { + + const char *embedTypeAs = NULL; + char buffer[256]; + if (types) { + types->ItemAt(index)->String(); + BMimeType mimeType(embedTypeAs); + + if (mimeType.GetShortDescription(buffer) == B_OK) + embedTypeAs = buffer; + } + + BString description; + if (specificItems->ItemAt(index)->Length()) { + description << (const BString &)(*specificItems->ItemAt(index)); + + if (embedTypeAs) + description << " (" << embedTypeAs << ")"; + + } else if (types) + description = embedTypeAs; + + const char *labelText; + char text[1024]; + if (actionText) { + int32 length = 1024 - 1 - (int32)strlen(actionText); + if (length > 0) { + description.Truncate(length); + sprintf(text, actionText, description.String()); + labelText = text; + } else + labelText = "label too long"; + } else + labelText = description.String(); + + menu->AddItem(new BMenuItem(labelText, 0)); + } + + menu->AddSeparatorItem(); + menu->AddItem(new BMenuItem("Cancel", 0)); + + int32 result = -1; + BMenuItem *resultingItem = menu->Go(where, false, true); + if (resultingItem) { + int32 index = menu->IndexOf(resultingItem); + if (index < count) + result = index; + } + + delete menu; + + return result; +} + + +bool +BPoseView::HandleMessageDropped(BMessage *message) +{ + ASSERT(message->WasDropped()); + + if (!fDropEnabled) + return false; + + if (!dynamic_cast(Window())) + return false; + + if (message->HasData("RGBColor", 'RGBC')) { + // do not handle roColor-style drops here, pass them on to the desktop + if (dynamic_cast(Window())) + BMessenger((BHandler *)Window()).SendMessage(message); + + return true; + } + + if (fDropTarget) + HiliteDropTarget(false); + + fDropTarget = NULL; + + ASSERT(TargetModel()); + BPoint offset; + BPoint dropPt(message->DropPoint(&offset)); + ConvertFromScreen(&dropPt); + + // tenatively figure out the pose we dropped the file onto + int32 index; + BPose *targetPose = FindPose(dropPt, &index); + Model tmpTarget; + Model *targetModel = NULL; + if (targetPose) { + targetModel = targetPose->TargetModel(); + if (targetModel->IsSymLink() + && tmpTarget.SetTo(targetPose->TargetModel()->EntryRef(), true, true) == B_OK) + targetModel = &tmpTarget; + } + + return HandleDropCommon(message, targetModel, targetPose, this, dropPt); +} + + +bool +BPoseView::HandleDropCommon(BMessage *message, Model *targetModel, BPose *targetPose, + BView *view, BPoint dropPt) +{ + uint32 buttons = (uint32)message->FindInt32("buttons"); + + BContainerWindow *containerWindow = NULL; + BPoseView *poseView = dynamic_cast(view); + if (poseView) + containerWindow = poseView->ContainerWindow(); + + // look for srcWindow to determine whether drag was initiated in tracker + BContainerWindow *srcWindow = NULL; + message->FindPointer("src_window", (void **) &srcWindow); + + if (!srcWindow) { + // drag was from another app + + if (targetModel == NULL) + targetModel = poseView->TargetModel(); + + // figure out if we dropped a file onto a directory and set the targetDirectory + // to it, else set it to this pose view + BDirectory targetDirectory; + if (targetModel && targetModel->IsDirectory()) + targetDirectory.SetTo(targetModel->EntryRef()); + + if (targetModel->IsRoot()) + // don't drop anyting into the root disk + return false; + + bool canCopy; + bool canMove; + bool canErase; + bool canLink; + if (FindDragNDropAction(message, canCopy, canMove, canLink, canErase)) { + // new D&D protocol + // what action can the drag initiator do? + if (canErase && CanTrashForeignDrag(targetModel)) { + BMessage reply(B_TRASH_TARGET); + message->SendReply(&reply); + return true; + } + + if ((canCopy || canMove) + && CanCopyOrMoveForeignDrag(targetModel, message)) { + // handle the promise style drag&drop + + // fish for specification of specialized menu items + BObjectList actionSpecifiers(10, true); + for (int32 index = 0; ; index++) { + const char *string; + if (message->FindString("be:actionspecifier", index, &string) != B_OK) + break; + + ASSERT(string); + actionSpecifiers.AddItem(new BString(string)); + } + + // build the list of types the drag originator offers + BObjectList types(10, true); + BObjectList typeNames(10, true); + for (int32 index = 0; ; index++) { + const char *string; + if (message->FindString("be:filetypes", index, &string) != B_OK) + break; + + ASSERT(string); + types.AddItem(new BString(string)); + + const char *typeName = ""; + message->FindString("be:type_descriptions", index, &typeName); + typeNames.AddItem(new BString(typeName)); + } + + int32 specificTypeIndex = -1; + int32 specificActionIndex = -1; + + // if control down, run a popup menu + if (canCopy + && ((modifiers() & B_CONTROL_KEY) || (buttons & B_SECONDARY_MOUSE_BUTTON))) { + + if (actionSpecifiers.CountItems() > 0) { + specificActionIndex = RunMimeTypeDestinationMenu(NULL, + NULL, &actionSpecifiers, view->ConvertToScreen(dropPt)); + + if (specificActionIndex == -1) + return false; + } else if (types.CountItems() > 0) { + specificTypeIndex = RunMimeTypeDestinationMenu("Create %s clipping", + &types, &typeNames, view->ConvertToScreen(dropPt)); + + if (specificTypeIndex == -1) + return false; + } + } + + char name[B_FILE_NAME_LENGTH]; + BFile file; + if (CreateClippingFile(poseView, file, name, &targetDirectory, message, + "Untitled clipping", !targetPose, dropPt) != B_OK) + return false; + + // here is a file for the drag initiator, it is up to it now to stuff it + // with the goods + + // build the reply message + BMessage reply(canCopy ? B_COPY_TARGET : B_MOVE_TARGET); + reply.AddString("be:types", B_FILE_MIME_TYPE); + if (specificTypeIndex != -1) { + // we had the user pick a specific type from a menu, use it + reply.AddString("be:filetypes", + types.ItemAt(specificTypeIndex)->String()); + + if (typeNames.ItemAt(specificTypeIndex)->Length()) + reply.AddString("be:type_descriptions", + typeNames.ItemAt(specificTypeIndex)->String()); + } + + if (specificActionIndex != -1) + // we had the user pick a specific type from a menu, use it + reply.AddString("be:actionspecifier", + actionSpecifiers.ItemAt(specificActionIndex)->String()); + + + reply.AddRef("directory", targetModel->EntryRef()); + reply.AddString("name", name); + + // Attach any data the originator may have tagged on + BMessage data; + if (message->FindMessage("be:originator-data", &data) == B_OK) + reply.AddMessage("be:originator-data", &data); + + // copy over all the file types the drag initiator claimed to + // support + for (int32 index = 0; ; index++) { + const char *type; + if (message->FindString("be:filetypes", index, &type) != B_OK) + break; + reply.AddString("be:filetypes", type); + } + + message->SendReply(&reply); + return true; + } + } + + if (message->HasRef("refs")) { + // ToDo: + // decide here on copy, move or create symlink + // look for specific command or bring up popup + // Unify this with local drag&drop + + if (!targetModel->IsDirectory()) + // bail if we are not a directory + return false; + + bool canRelativeLink = false; + if (!canCopy && !canMove && !canLink && containerWindow) { + if (((buttons & B_SECONDARY_MOUSE_BUTTON) + || (modifiers() & B_CONTROL_KEY))) { + switch (containerWindow->ShowDropContextMenu(dropPt)) { + case kCreateRelativeLink: + canRelativeLink = true; + break; + case kCreateLink: + canLink = true; + break; + case kMoveSelectionTo: + canMove = true; + break; + case kCopySelectionTo: + canCopy = true; + break; + case kCancelButton: + default: + // user canceled context menu + return true; + } + } else + canCopy = true; + } + + uint32 moveMode; + if (canCopy) + moveMode = kCopySelectionTo; + else if (canMove) + moveMode = kMoveSelectionTo; + else if (canLink) + moveMode = kCreateLink; + else if (canRelativeLink) + moveMode = kCreateRelativeLink; + else { + TRESPASS(); + return true; + } + + // handle refs by performing a copy + BObjectList *entryList = new BObjectList(); + + for (int32 index = 0; ; index++) { + // copy all enclosed refs into a list + entry_ref ref; + if (message->FindRef("refs", index, &ref) != B_OK) + break; + entryList->AddItem(new entry_ref(ref)); + } + + int32 count = entryList->CountItems(); + if (count) { + BList *pointList = 0; + if (poseView && !targetPose) { + // calculate a pointList to make the icons land were we dropped them + pointList = new BList(count); + // force the the icons to lay out in 5 columns + for (int32 index = 0; count; index++) { + for (int32 j = 0; count && j < 4; j++, count--) { + BPoint point(dropPt + BPoint(j * poseView->fGrid.x, index * + poseView->fGrid.y)); + pointList->AddItem(new BPoint(poseView->PinToGrid(point, + poseView->fGrid, poseView->fOffset))); + } + } + } + + // perform asynchronous copy + FSMoveToFolder(entryList, new BEntry(targetModel->EntryRef()), + moveMode, pointList); + + return true; + } + + // nothing to copy, list doesn't get consumed + delete entryList; + return true; + } + if (message->HasData(kPlainTextMimeType, B_MIME_TYPE)) { + // text dropped, make into a clipping file + if (!targetModel->IsDirectory()) + // bail if we are not a directory + return false; + + // find the text + int32 textLength; + const char *text; + if (message->FindData(kPlainTextMimeType, B_MIME_TYPE, (const void **)&text, + &textLength) != B_OK) + return false; + + char name[B_FILE_NAME_LENGTH]; + + BFile file; + if (CreateClippingFile(poseView, file, name, &targetDirectory, message, + "Untitled clipping", !targetPose, dropPt) != B_OK) + return false; + + // write out the file + if (file.Seek(0, SEEK_SET) == B_ERROR + || file.Write(text, (size_t)textLength) < 0 + || file.SetSize(textLength) != B_OK) { + // failed to write file, remove file and bail + file.Unset(); + BEntry entry(&targetDirectory, name); + entry.Remove(); + PRINT(("error writing text into file %s\n", name)); + } + + // pick up TextView styles if available and save them with the file + const text_run_array *textRuns = NULL; + int32 dataSize = 0; + if (message->FindData("application/x-vnd.Be-text_run_array", B_MIME_TYPE, + (const void **)&textRuns, &dataSize) == B_OK && textRuns && dataSize) { + // save styles the same way StyledEdit does + void *data = BTextView::FlattenRunArray(textRuns, &dataSize); + file.WriteAttr("styles", B_RAW_TYPE, 0, data, (size_t)dataSize); + free(data); + } + + // mark as a clipping file + int32 tmp; + file.WriteAttr(kAttrClippingFile, B_RAW_TYPE, 0, &tmp, sizeof(int32)); + + // set the file type + BNodeInfo info(&file); + info.SetType(kPlainTextMimeType); + + return true; + } + if (message->HasData(kBitmapMimeType, B_MESSAGE_TYPE) + || message->HasData(kLargeIconType, B_MESSAGE_TYPE) + || message->HasData(kMiniIconType, B_MESSAGE_TYPE)) { + // bitmap, make into a clipping file + if (!targetModel->IsDirectory()) + // bail if we are not a directory + return false; + + BMessage embeddedBitmap; + if (message->FindMessage(kBitmapMimeType, &embeddedBitmap) != B_OK + && message->FindMessage(kLargeIconType, &embeddedBitmap) != B_OK + && message->FindMessage(kMiniIconType, &embeddedBitmap) != B_OK) + return false; + + char name[B_FILE_NAME_LENGTH]; + + BFile file; + if (CreateClippingFile(poseView, file, name, &targetDirectory, message, + "Untitled bitmap", !targetPose, dropPt) != B_OK) + return false; + + int32 size = embeddedBitmap.FlattenedSize(); + if (size > 1024*1024) + // bail if too large + return false; + + char *buffer = new char [size]; + embeddedBitmap.Flatten(buffer, size); + + // write out the file + if (file.Seek(0, SEEK_SET) == B_ERROR + || file.Write(buffer, (size_t)size) < 0 + || file.SetSize(size) != B_OK) { + // failed to write file, remove file and bail + file.Unset(); + BEntry entry(&targetDirectory, name); + entry.Remove(); + PRINT(("error writing bitmap into file %s\n", name)); + } + + // mark as a clipping file + int32 tmp; + file.WriteAttr(kAttrClippingFile, B_RAW_TYPE, 0, &tmp, sizeof(int32)); + + // set the file type + BNodeInfo info(&file); + info.SetType(kBitmapMimeType); + + return true; + } + return false; + } + + if (srcWindow == containerWindow) { + // drag started in this window + containerWindow->Activate(); + containerWindow->UpdateIfNeeded(); + poseView->ResetPosePlacementHint(); + } + + if (srcWindow == containerWindow && DragSelectionContains(targetPose, message)) { + // drop on self + targetModel = NULL; + } + + bool wasHandled = false; + bool ignoreTypes = (modifiers() & B_CONTROL_KEY) != 0; + + if (targetModel) { + // ToDo: + // pick files to drop/launch on a case by case basis + if (targetModel->IsDirectory()) { + MoveSelectionInto(targetModel, srcWindow, containerWindow, buttons, dropPt, + false); + wasHandled = true; + } else if (CanHandleDragSelection(targetModel, message, ignoreTypes)) { + LaunchAppWithSelection(targetModel, message, !ignoreTypes); + wasHandled = true; + } + } + + if (poseView && !wasHandled) { + BPoint clickPt = message->FindPoint("click_pt"); + // ToDo: + // removed check for root here need to do that, possibly at a different + // level + poseView->MoveSelectionTo(dropPt, clickPt, srcWindow); + } + + if (poseView && poseView->fEnsurePosesVisible) + poseView->CheckPoseVisibility(); + + return true; +} + + +struct LaunchParams { + Model *app; + bool checkTypes; + BMessage *refsMessage; +}; + + +static bool +AddOneToLaunchMessage(BPose *pose, BPoseView *, void *castToParams) +{ + LaunchParams *params = (LaunchParams *)castToParams; + + ASSERT(pose->TargetModel()); + if (params->app->IsDropTarget(params->checkTypes ? pose->TargetModel() : 0, true)) + params->refsMessage->AddRef("refs", pose->TargetModel()->EntryRef()); + + return false; +} + + +void +BPoseView::LaunchAppWithSelection(Model *appModel, const BMessage *dragMessage, + bool checkTypes) +{ + // launch items from the current selection with ; only pass the same + // files that we previously decided can be handled by + BMessage refs(B_REFS_RECEIVED); + LaunchParams params; + params.app = appModel; + params.checkTypes = checkTypes; + params.refsMessage = &refs; + + // add Tracker token so that refs received recipients can script us + BContainerWindow *srcWindow; + dragMessage->FindPointer("src_window", (void **)&srcWindow); + if (srcWindow) + params.refsMessage->AddMessenger("TrackerViewToken", BMessenger( + srcWindow->PoseView())); + + EachItemInDraggedSelection(dragMessage, AddOneToLaunchMessage, 0, ¶ms); + if (params.refsMessage->HasRef("refs")) + TrackerLaunch(appModel->EntryRef(), params.refsMessage, true); +} + + +static bool +OneMatches(BPose *pose, BPoseView *, void *castToPose) +{ + return pose == (const BPose *)castToPose; +} + + +bool +BPoseView::DragSelectionContains(const BPose *target, + const BMessage *dragMessage) +{ + return EachItemInDraggedSelection(dragMessage, OneMatches, 0, (void *)target); +} + + +static void +CopySelectionListToBListAsEntryRefs(const PoseList *original, BObjectList *copy) +{ + int32 count = original->CountItems(); + for (int32 index = 0; index < count; index++) + copy->AddItem(new entry_ref(*(original->ItemAt(index)->TargetModel()->EntryRef()))); +} + + +void +BPoseView::MoveSelectionInto(Model *destFolder, BContainerWindow *srcWindow, + bool forceCopy, bool createLink, bool relativeLink) +{ + uint32 buttons; + BPoint loc; + GetMouse(&loc, &buttons); + MoveSelectionInto(destFolder, srcWindow, dynamic_cast(Window()), + buttons, loc, forceCopy, createLink, relativeLink); +} + + +void +BPoseView::MoveSelectionInto(Model *destFolder, BContainerWindow *srcWindow, + BContainerWindow *destWindow, uint32 buttons, BPoint loc, bool forceCopy, + bool createLink, bool relativeLink) +{ + AutoLock lock(srcWindow); + if (!lock) + return; + + ASSERT(srcWindow->PoseView()->TargetModel()); + + // make sure source and destination folders are different + if (!createLink && (*srcWindow->PoseView()->TargetModel()->NodeRef() + == *destFolder->NodeRef())) + return; + + bool createRelativeLink = relativeLink; + if (((buttons & B_SECONDARY_MOUSE_BUTTON) + || (modifiers() & B_CONTROL_KEY)) && destWindow) { + + switch (destWindow->ShowDropContextMenu(loc)) { + case kCreateRelativeLink: + createRelativeLink = true; + break; + + case kCreateLink: + createLink = true; + break; + + case kMoveSelectionTo: + break; + + case kCopySelectionTo: + forceCopy = true; + break; + + case kCancelButton: + default: + // user canceled context menu + return; + } + } + + BEntry *destEntry = new BEntry(destFolder->EntryRef()); + bool destIsTrash = FSIsTrashDir(destEntry); + + // perform asynchronous copy/move + forceCopy = forceCopy || (modifiers() & B_OPTION_KEY); + + bool okToMove = true; + + if (destFolder->IsRoot()) { + (new BAlert("", kNoCopyToRootStr, "Cancel", NULL, NULL, + B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); + okToMove = false; + } + + // can't copy items into the trash + if (forceCopy && destIsTrash) { + (new BAlert("", kNoCopyToTrashStr, "Cancel", NULL, NULL, + B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); + okToMove = false; + } + + // can't create symlinks into the trash + if (createLink && destIsTrash) { + (new BAlert("", kNoLinkToTrashStr, "Cancel", NULL, NULL, + B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); + okToMove = false; + } + + // prompt user if drag was from a query + if (srcWindow->TargetModel()->IsQuery() + && !forceCopy && !destIsTrash && !createLink) { + srcWindow->UpdateIfNeeded(); + okToMove = (new BAlert("", kOkToMoveStr, "Cancel", "Move", NULL, + B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go() == 1; + } + + if (okToMove) { + PoseList *selectionList = srcWindow->PoseView()->SelectionList(); + BObjectList *srcList = new BObjectList( + selectionList->CountItems(), true); + CopySelectionListToBListAsEntryRefs(selectionList, srcList); + + uint32 moveMode; + if (forceCopy) + moveMode = kCopySelectionTo; + else if (createRelativeLink) + moveMode = kCreateRelativeLink; + else if (createLink) + moveMode = kCreateLink; + else + moveMode = kMoveSelectionTo; + + FSMoveToFolder(srcList, destEntry, moveMode); + return; + } + + delete destEntry; +} + + +void +BPoseView::MoveSelectionTo(BPoint dropPt, BPoint clickPt, + BContainerWindow* srcWindow) +{ + // Moves selection from srcWindow into this window, copying if necessary. + + BContainerWindow *window = ContainerWindow(); + if (!window) + return; + + ASSERT(window->PoseView()); + ASSERT(TargetModel()); + + // make sure this window is a legal drop target + if (srcWindow != window && !TargetModel()->IsDropTarget()) + return; + + // if drop was done with control key or secondary button + // then we need to show a context menu for drop location + uint32 buttons = (uint32)window->CurrentMessage()->FindInt32("buttons"); + bool createLink = false; + bool forceCopy = false; + bool createRelativeLink = false; + bool dropOnGrid = (modifiers() & B_COMMAND_KEY) != 0; + + if ((buttons & B_SECONDARY_MOUSE_BUTTON) || (modifiers() & B_CONTROL_KEY)) { + + switch (window->ShowDropContextMenu(dropPt)) { + case kCreateRelativeLink: + createRelativeLink = true; + break; + + case kCreateLink: + createLink = true; + break; + + case kMoveSelectionTo: + break; + + case kCopySelectionTo: + if (srcWindow == window) { + DuplicateSelection(&clickPt, &dropPt); + return; + } + forceCopy = true; + break; + + case kCancelButton: + default: + // user canceled context menu + return; + } + } + + if (!createLink && !createRelativeLink && srcWindow == window) { // dropped in same window + if (ViewMode() == kListMode) // can't move in list view + return; + + BPoint delta(dropPt - clickPt); + int32 count = fSelectionList->CountItems(); + for (int32 index = 0; index < count; index++) { + BPose *pose = fSelectionList->ItemAt(index); + + // remove pose from VSlist before changing location + // so that we "find" the correct pose to remove + // need to do this because bsearch uses top of pose + // to locate pose to remove + RemoveFromVSList(pose); + + BRect oldBounds(pose->CalcRect(this)); + BPoint location(pose->Location() + delta); + if (dropOnGrid) + location = PinToGrid(location, fGrid, fOffset); + + pose->MoveTo(location, this); + + RemoveFromExtent(oldBounds); + AddToExtent(pose->CalcRect(this)); + + // remove and reinsert pose to keep VSlist sorted + AddToVSList(pose); + } + } else { + AutoLock lock(srcWindow); + if (!lock) + return; + + // dropped from another window + // CopyTask will delete pointList + PoseList *selectionList = srcWindow->PoseView()->SelectionList(); + int32 count = selectionList->CountItems(); + BList *pointList = GetDropPointList(clickPt, dropPt, selectionList, + srcWindow->PoseView()->ViewMode() == kListMode, dropOnGrid); + + // perform asynch copy/move + forceCopy = forceCopy || (modifiers() & B_OPTION_KEY); + bool okToMove = true; + BEntry *destEntry = new BEntry(TargetModel()->EntryRef()); + bool destIsTrash = FSIsTrashDir(destEntry); + + // don't prompt if we're going to end up copying anyway + if (srcWindow->PoseView()->TargetModel()->IsQuery() + && !forceCopy + && !createLink + && !destIsTrash) { + srcWindow->UpdateIfNeeded(); + okToMove = (new BAlert("", kOkToMoveStr, "Cancel", "Move", NULL, + B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go() == 1; + } + + // can't copy items into the trash + if (forceCopy && destIsTrash) { + (new BAlert("", kNoCopyToTrashStr, "Cancel", NULL, NULL, + B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); + okToMove = false; + } + + // can't create symlinks into the trash + if ((createLink || createRelativeLink) && destIsTrash) { + (new BAlert("", kNoLinkToTrashStr, "Cancel", NULL, NULL, + B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); + okToMove = false; + } + + if (okToMove) { + // create dup Model list, dest Model for CopyTask + BObjectList *srcList = new BObjectList(count, true); + CopySelectionListToBListAsEntryRefs(selectionList, srcList); + uint32 moveMode; + if (forceCopy) + moveMode = kCopySelectionTo; + else if (createRelativeLink) + moveMode = kCreateRelativeLink; + else if (createLink) + moveMode = kCreateLink; + else + moveMode = kMoveSelectionTo; + FSMoveToFolder(srcList, destEntry, moveMode, pointList); + } else { + if (pointList) { + pointList->DoForEach(delete_point); + delete pointList; + } + delete destEntry; + } + } +} + + +inline void +UpdateWasBrokenSymlinkBinder(BPose *pose, Model *, BPoseView *poseView, + BPoint *loc) +{ + pose->UpdateWasBrokenSymlink(*loc, poseView); + loc->y += poseView->ListElemHeight(); +} + + +void +BPoseView::TryUpdatingBrokenLinks() +{ + AutoLock lock(Window()); + if (!lock) + return; + + // try fixing broken symlinks + BPoint loc; + EachPoseAndModel(fPoseList, &UpdateWasBrokenSymlinkBinder, this, &loc); +} + + +void +BPoseView::RemoveNonBootDesktopModels(BPose *, Model *model, int32, + BPoseView *poseView, dev_t) +{ + BPath path; + + model->GetPath(&path); + + TrackerString pathString(path.Path()); + + if (pathString.Contains("/home/Desktop") && !pathString.StartsWith("/boot")) + poseView->DeletePose(model->NodeRef()); +} + + +void +BPoseView::PoseHandleDeviceUnmounted(BPose *pose, Model *model, int32 index, + BPoseView *poseView, dev_t device) +{ + if (model->NodeRef()->device == device) + poseView->DeletePose(model->NodeRef()); + else if (model->IsSymLink() + && model->LinkTo() + && model->LinkTo()->NodeRef()->device == device) + poseView->DeleteSymLinkPoseTarget(model->LinkTo()->NodeRef(), pose, index); +} + + +static void +OneMetaMimeChanged(BPose *pose, Model *model, int32 index, + BPoseView *poseView, const char *type) +{ + ASSERT(model); + if (model->IconFrom() != kNode + && model->IconFrom() != kUnknownSource + && model->IconFrom() != kUnknownNotFromNode + // ToDo: + // add supertype compare + && strcasecmp(model->MimeType(), type) == 0) { + // metamime change very likely affected the documents icon + + BPoint poseLoc(0, index * poseView->ListElemHeight()); + pose->UpdateIcon(poseLoc, poseView); + } +} + + +void +BPoseView::MetaMimeChanged(const char *type, const char *preferredApp) +{ + IconCache::sIconCache->IconChanged(type, preferredApp); + // wait for other windows to do the same before we start + // updating poses which causes icon recaching + snooze(200000); + + EachPoseAndResolvedModel(fPoseList, &OneMetaMimeChanged, this, type); +} + + +class MetaMimeChangedAccumulator : public AccumulatingFunctionObject { +// pools up matching metamime change notices, executing them as a single +// update +public: + MetaMimeChangedAccumulator(void (BPoseView::*func)(const char *type, + const char *preferredApp), + BContainerWindow *window, const char *type, const char *preferredApp) + : fCallOnThis(window), + fFunc(func), + fType(type), + fPreferredApp(preferredApp) + {} + + virtual bool CanAccumulate(const AccumulatingFunctionObject *functor) const + { + return dynamic_cast(functor) + && dynamic_cast(functor)->fType + == fType + && dynamic_cast(functor)-> + fPreferredApp == fPreferredApp; + } + + virtual void Accumulate(AccumulatingFunctionObject *DEBUG_ONLY(functor)) + { + ASSERT(CanAccumulate(functor)); + // do nothing, no further accumulating needed + } + +protected: + virtual void operator()() + { + AutoLock lock(fCallOnThis); + if (!lock) + return; + + (fCallOnThis->PoseView()->*fFunc)(fType.String(), fPreferredApp.String()); + } + + virtual ulong Size() const + { + return sizeof (*this); + } + +private: + BContainerWindow *fCallOnThis; + void (BPoseView::*fFunc)(const char *type, const char *preferredApp); + BString fType; + BString fPreferredApp; +}; + + +bool +BPoseView::NoticeMetaMimeChanged(const BMessage *message) +{ + int32 change; + if (message->FindInt32("be:which", &change) != B_OK) + return true; + + bool iconChanged = (change & B_ICON_CHANGED) != 0; + bool iconForTypeChanged = (change & B_ICON_FOR_TYPE_CHANGED) != 0; + bool preferredAppChanged = (change & B_APP_HINT_CHANGED) + || (change & B_PREFERRED_APP_CHANGED); + + const char *type = NULL; + const char *preferredApp = NULL; + + if (iconChanged || preferredAppChanged) + message->FindString("be:type", &type); + + if (iconForTypeChanged) { + message->FindString("be:extra_type", &type); + message->FindString("be:type", &preferredApp); + } + + if (iconChanged || preferredAppChanged || iconForTypeChanged) { + TaskLoop *taskLoop = ContainerWindow()->DelayedTaskLoop(); + ASSERT(taskLoop); + taskLoop->AccumulatedRunLater(new MetaMimeChangedAccumulator( + &BPoseView::MetaMimeChanged, ContainerWindow(), type, preferredApp), + 200000, 5000000); + } + return true; +} + + +bool +BPoseView::FSNotification(const BMessage *message) +{ + node_ref itemNode; + dev_t device; + + switch (message->FindInt32("opcode")) { + case B_ENTRY_CREATED: + { + message->FindInt32("device", &itemNode.device); + node_ref dirNode; + dirNode.device = itemNode.device; + message->FindInt64("directory", (int64 *)&dirNode.node); + message->FindInt64("node", (int64 *)&itemNode.node); + + ASSERT(TargetModel()); + + // Query windows can get notices on different dirNodes + // The Disks window can too + // So can the Desktop, as long as the integrate flag is on + TrackerSettings settings; + if (dirNode != *TargetModel()->NodeRef() + && !TargetModel()->IsQuery() + && !TargetModel()->IsRoot() + && ((!settings.IntegrateNonBootBeOSDesktops() + && !settings.ShowDisksIcon()) || !IsDesktopView())) + // stray notification + break; + + const char *name; + if (message->FindString("name", &name) == B_OK) + EntryCreated(&dirNode, &itemNode, name); +#if DEBUG + else + SERIAL_PRINT(("no name in entry creation message\n")); +#endif + break; + } + case B_ENTRY_MOVED: + return EntryMoved(message); + break; + + case B_ENTRY_REMOVED: + message->FindInt32("device", &itemNode.device); + message->FindInt64("node", (int64 *)&itemNode.node); + + // our window itself may be deleted + // we must check to see if this comes as a query + // notification or a node monitor notification because + // if it's a query notification then we're just being told we + // no longer match the query, so we don't want to close the window + // but it's a node monitor notification then that means our query + // file has been deleted so we close the window + + if (message->what == B_NODE_MONITOR + && TargetModel() && *(TargetModel()->NodeRef()) == itemNode) { + if (!TargetModel()->IsRoot()) { + // it is impossible to watch for ENTRY_REMOVED in "/" because the + // notification is ambiguous - the vnode is that of the volume but + // the device is of the parent not the same as the device of the volume + // that way we may get aliasing for volumes with vnodes of 1 + // (currently the case for iso9660) + DisableSaveLocation(); + Window()->Close(); + } + } else { + int32 index; + BPose *pose = fPoseList->FindPose(&itemNode, &index); + if (!pose) { + // couldn't find pose, first check if the node might be + // target of a symlink pose; + // + // What happens when a node and a symlink to it are in the + // same window? + // They get monitored twice, we get two notifications; the + // first one will get caught by the first FindPose, the + // second one by the DeepFindPose + // + pose = fPoseList->DeepFindPose(&itemNode, &index); + if (pose) { + DeleteSymLinkPoseTarget(&itemNode, pose, index); + break; + } + } + return DeletePose(&itemNode); + } + break; + + case B_DEVICE_MOUNTED: + { + if (message->FindInt32("new device", &device) != B_OK) + break; + + if (TargetModel() != NULL && TargetModel()->IsRoot()) { + BVolume volume(device); + CreateVolumePose(&volume, false); + } else if (ContainerWindow()->IsTrash()) { + // add trash items from newly mounted volume + + BDirectory trashDir; + BEntry entry; + BVolume volume(device); + if (FSGetTrashDir(&trashDir, volume.Device()) == B_OK + && trashDir.GetEntry(&entry) == B_OK) { + Model model(&entry); + if (model.InitCheck() == B_OK) + AddPoses(&model); + } + } + TaskLoop *taskLoop = ContainerWindow()->DelayedTaskLoop(); + ASSERT(taskLoop); + taskLoop->RunLater(NewMemberFunctionObject( + &BPoseView::TryUpdatingBrokenLinks, this), 500000); + // delay of 500000: wait for volumes to properly finish mounting + // without this in the Model::FinishSettingUpType a symlink + // to a volume would get initialized as a symlink to a directory + // because IsRootDirectory looks like returns false. Either there + // is a race condition or I was doing something wrong. + break; + } + case B_DEVICE_UNMOUNTED: + if (message->FindInt32("device", &device) == B_OK) { + if (TargetModel() && TargetModel()->NodeRef()->device == device) { + // close the window from a volume that is gone + DisableSaveLocation(); + Window()->Close(); + } else if (TargetModel()) + EachPoseAndModel(fPoseList, &PoseHandleDeviceUnmounted, this, device); + } + break; + + case B_STAT_CHANGED: + case B_ATTR_CHANGED: + return AttributeChanged(message); + break; + } + return true; +} + + +bool +BPoseView::CreateSymlinkPoseTarget(Model *symlink) +{ + Model *newResolvedModel = NULL; + Model *result = symlink->LinkTo(); + + if (!result) { + newResolvedModel = new Model(symlink->EntryRef(), true, true); + WatchNewNode(newResolvedModel->NodeRef()); + // this should be called before creating the model + + if (newResolvedModel->InitCheck() != B_OK) { + // broken link, still can show though, bail + watch_node(newResolvedModel->NodeRef(), B_STOP_WATCHING, this); + delete newResolvedModel; + return true; + } + result = newResolvedModel; + } + + BModelOpener opener(result); + // open the model + + PoseInfo poseInfo; + ReadPoseInfo(result, &poseInfo); + + if (!ShouldShowPose(result, &poseInfo)) { + // symlink target invisible, make the link to it the same + watch_node(newResolvedModel->NodeRef(), B_STOP_WATCHING, this); + delete newResolvedModel; + // clean up what we allocated + return false; + } + + symlink->SetLinkTo(result); + // watch the link target too + return true; +} + + +BPose * +BPoseView::EntryCreated(const node_ref *dirNode, const node_ref *itemNode, + const char *name, int32 *indexPtr) +{ + // reject notification if pose already exists + if (fPoseList->FindPose(itemNode) || FindZombie(itemNode)) + return NULL; + BPoseView::WatchNewNode(itemNode); + // have to node monitor ahead of time because Model will + // cache up the file type and preferred app + Model *model = new Model(dirNode, itemNode, name, true); + if (model->InitCheck() != B_OK) { + // if we have trouble setting up model then we stuff it into + // a zombie list in a half-alive state until we can properly awaken it + PRINT(("2 adding model %s to zombie list, error %s\n", model->Name(), + strerror(model->InitCheck()))); + fZombieList->AddItem(model); + return NULL; + } + + // get saved pose info out of attribute + PoseInfo poseInfo; + ReadPoseInfo(model, &poseInfo); + + if (!ShouldShowPose(model, &poseInfo) + // filter out undesired poses + || (model->IsSymLink() && !CreateSymlinkPoseTarget(model))) { + // model is a symlink, cache up the symlink target or scrap + // everything if target is invisible + watch_node(model->NodeRef(), B_STOP_WATCHING, this); + delete model; + return NULL; + } + + return CreatePose(model, &poseInfo, true, indexPtr); +} + + +bool +BPoseView::EntryMoved(const BMessage *message) +{ + ino_t oldDir; + node_ref dirNode; + node_ref itemNode; + + message->FindInt32("device", &dirNode.device); + itemNode.device = dirNode.device; + message->FindInt64("to directory", (int64 *)&dirNode.node); + message->FindInt64("node", (int64 *)&itemNode.node); + message->FindInt64("from directory", (int64 *)&oldDir); + + const char *name; + if (message->FindString("name", &name) != B_OK) + return true; + // handle special case of notifying a name change for a volume + // - the notification is not enough, because the volume's device + // is different than that of the root directory; we have to do a + // lookup using the new volume name and get the volume device from there + StatStruct st; + // get the inode of the root and check if we got a notification on it + if (stat("/", &st) >= 0 + && st.st_dev == dirNode.device + && st.st_ino == dirNode.node) { + + BString buffer; + buffer << "/" << name; + if (stat(buffer.String(), &st) >= 0) { + // point the dirNode to the actual volume + itemNode.node = st.st_ino; + itemNode.device = st.st_dev; + } + } + + ASSERT(TargetModel()); + + node_ref thisDirNode; + if (ContainerWindow()->IsTrash()) { + + BDirectory trashDir; + if (FSGetTrashDir(&trashDir, itemNode.device) != B_OK) + return true; + + trashDir.GetNodeRef(&thisDirNode); + } else + thisDirNode = *TargetModel()->NodeRef(); + + // see if we need to update window title (and folder itself) + if (thisDirNode == itemNode) { + + TargetModel()->UpdateEntryRef(&dirNode, name); + assert_cast(Window())->UpdateTitle(); + } + if (oldDir == dirNode.node || TargetModel()->IsQuery()) { + + // rename or move of entry in this directory (or query) + + int32 index; + BPose *pose = fPoseList->FindPose(&itemNode, &index); + + if (pose) { + pose->TargetModel()->UpdateEntryRef(&dirNode, name); + // for queries we check for move to trash and remove item if so + if (TargetModel()->IsQuery()) { + PoseInfo poseInfo; + ReadPoseInfo(pose->TargetModel(), &poseInfo); + if (!ShouldShowPose(pose->TargetModel(), &poseInfo)) + return DeletePose(&itemNode, pose, index); + } + + BPoint loc(0, index * fListElemHeight); + // if we get a rename then we need to assume that we might + // have missed some other attr changed notifications so we + // recheck all widgets + if (pose->TargetModel()->OpenNode() == B_OK) { + pose->UpdateAllWidgets(index, loc, this); + pose->TargetModel()->CloseNode(); + CheckPoseSortOrder(pose, index); + } + } else { + // also must watch for renames on zombies + Model *zombie = FindZombie(&itemNode, &index); + if (zombie) { + PRINT(("converting model %s from a zombie\n", zombie->Name())); + zombie->UpdateEntryRef(&dirNode, name); + pose = ConvertZombieToPose(zombie, index); + } else + return false; + } + if (pose) + pendingNodeMonitorCache.PoseCreatedOrMoved(this, pose); + } else if (oldDir == thisDirNode.node) + return DeletePose(&itemNode); + else if (dirNode.node == thisDirNode.node) + EntryCreated(&dirNode, &itemNode, name); + else if (TrackerSettings().IntegrateNonBootBeOSDesktops() && IsDesktopView()) { + // node entered/exited desktop view, we have more work to do + + // if old dir node is a desktop folder, delete pose + node_ref oldDirNode; + oldDirNode.node = oldDir; + oldDirNode.device = dirNode.device; + BDirectory oldDirectory(&oldDirNode); + BEntry oldDirectoryEntry; + oldDirectory.GetEntry(&oldDirectoryEntry); + if (oldDirectoryEntry.InitCheck() == B_OK + && FSIsDeskDir(&oldDirectoryEntry) + && !DeletePose(&itemNode)) + return false; + + // if new dir node is a desktop folder, create pose + BDirectory newDirectory(&dirNode); + BEntry newDirectoryEntry; + newDirectory.GetEntry(&newDirectoryEntry); + if (newDirectoryEntry.InitCheck() == B_OK && FSIsDeskDir(&newDirectoryEntry)) + EntryCreated(&dirNode, &itemNode, name); + } + return true; +} + + +bool +BPoseView::AttributeChanged(const BMessage *message) +{ + node_ref itemNode; + message->FindInt32("device", &itemNode.device); + message->FindInt64("node", (int64 *)&itemNode.node); + + const char *attrName; + message->FindString("attr", &attrName); + + int32 index; + BPose *pose = fPoseList->DeepFindPose(&itemNode, &index); + if (pose) { + attr_info info; + BPoint loc(0, index * fListElemHeight); + + Model *model = pose->TargetModel(); + if (model->IsSymLink() && *model->NodeRef() != itemNode) + // change happened on symlink's target + model = model->ResolveIfLink(); + ASSERT(model); + + status_t result = B_OK; + for (int32 count = 0; count < 100; count++) { + // if node is busy, wait a little, it may be in the + // middle of mimeset and we wan't to pick up the changes + result = model->OpenNode(); + if (result == B_OK || result != B_BUSY) + break; + + PRINT(("model %s busy, retrying in a bit\n", model->Name())); + snooze(10000); + } + + if (result == B_OK) { + if (attrName && model->Node()) { + info.type = 0; + // the call below might fail if the attribute has been removed + model->Node()->GetAttrInfo(attrName, &info); + pose->UpdateWidgetAndModel(model, attrName, info.type, index, loc, this); + } else + pose->UpdateWidgetAndModel(model, 0, 0, index, loc, this); + + model->CloseNode(); + } else { + PRINT(("Cache Error %s\n", strerror(result))); + return false; + } + + uint32 attrHash; + if (attrName) { + // rebuild the MIME type list, if the MIME type has changed + if (strcmp(attrName, kAttrMIMEType) == 0) + RefreshMimeTypeList(); + + // note: the following code is wrong, because this sort of hashing + // may overlap and we get aliasing + attrHash = AttrHashString(attrName, info.type); + } + if (!attrName || attrHash == PrimarySort() || attrHash == SecondarySort()) + CheckPoseSortOrder(pose, index); + } else { + // pose might be in zombie state if we're copying... + Model *zombie = FindZombie(&itemNode, &index); + if (zombie) { + PRINT(("converting model %s from a zombie\n", zombie->Name())); + ConvertZombieToPose(zombie, index); + } else { + // did not find a pose, probably not entered yet + // PRINT(("failed to deliver attr change node monitor - pose not found\n")); + return false; + } + } + + return true; +} + + +void +BPoseView::UpdateVolumeIcon(dev_t device, bool forceUpdate) +{ + int32 index; + BPose *pose = fPoseList->FindVolumePose(device,&index); + if (pose == NULL) + return; + + if (pose->UpdateVolumeSpaceBar(TrackerSettings().ShowVolumeSpaceBar()) || forceUpdate) { + BPoint loc(0, index * fListElemHeight); + pose->UpdateIcon(loc, this); + } +} + + +void +BPoseView::UpdateVolumeIcons() +{ + BVolumeRoster roster; + + BVolume volume; + while(roster.GetNextVolume(&volume) == B_NO_ERROR) { + BDirectory dir; + volume.GetRootDirectory(&dir); + node_ref nodeRef; + dir.GetNodeRef(&nodeRef); + + UpdateVolumeIcon(nodeRef.device, true); + } +} + + +BPose * +BPoseView::ConvertZombieToPose(Model *zombie, int32 index) +{ + if (zombie->UpdateStatAndOpenNode() != B_OK) + return NULL; + + fZombieList->RemoveItemAt(index); + + PoseInfo poseInfo; + ReadPoseInfo(zombie, &poseInfo); + + if (ShouldShowPose(zombie, &poseInfo)) + // ToDo: + // handle symlinks here + return CreatePose(zombie, &poseInfo); + + delete zombie; + + return NULL; +} + + +BList * +BPoseView::GetDropPointList(BPoint dropStart, BPoint dropEnd, const PoseList *poses, + bool sourceInListMode, bool dropOnGrid) const +{ + if (ViewMode() == kListMode) + return NULL; + + int32 count = poses->CountItems(); + BList *pointList = new BList(count); + for (int32 index = 0; index < count; index++) { + BPose *pose = poses->ItemAt(index); + BPoint poseLoc; + if (sourceInListMode) + poseLoc = dropEnd + BPoint(0, index * (IconPoseHeight() + 3)); + else + poseLoc = dropEnd + (pose->Location() - dropStart); + + if (dropOnGrid) + poseLoc = PinToGrid(poseLoc, fGrid, fOffset); + + pointList->AddItem(new BPoint(poseLoc)); + } + + return pointList; +} + + +void +BPoseView::DuplicateSelection(BPoint *dropStart, BPoint *dropEnd) +{ + // If there is a volume or trash folder, remove them from the list + // because they cannot get copied + int32 selectionSize = fSelectionList->CountItems(); + for (int32 index = 0; index < selectionSize; index++) { + BPose *pose = (BPose*)fSelectionList->ItemAt(index); + Model *model = pose->TargetModel(); + + // can't duplicate a volume or the trash + BEntry entry(model->EntryRef()); + if (FSIsTrashDir(&entry) || model->IsVolume()) { + fSelectionList->RemoveItemAt(index); + index--; + selectionSize--; + if (fSelectionPivotPose == pose) + fSelectionPivotPose = NULL; + if (fRealPivotPose == pose) + fRealPivotPose = NULL; + continue; + } + } + + // create entry_ref list from selection + if (!fSelectionList->IsEmpty()) { + BObjectList *srcList = new BObjectList( + fSelectionList->CountItems(), true); + CopySelectionListToBListAsEntryRefs(fSelectionList, srcList); + + BList *dropPoints = NULL; + if (dropStart) + dropPoints = GetDropPointList(*dropStart, *dropEnd, fSelectionList, + ViewMode() == kListMode, (modifiers() & B_COMMAND_KEY) != 0); + + // perform asynchronous duplicate + FSDuplicate(srcList, dropPoints); + } +} + + +void +BPoseView::SelectPoseAtLocation(BPoint point) +{ + int32 index; + BPose *pose = FindPose(point, &index); + if (pose) + SelectPose(pose, index); +} + + +void +BPoseView::MoveListToTrash(BObjectList *list, bool selectNext, + bool deleteDirectly) +{ + if (!list->CountItems()) + return; + + BObjectList *taskList = + new BObjectList(2, true); + // new owning list of tasks + + // first move selection to trash, + if (deleteDirectly) + taskList->AddItem(NewFunctionObject(FSDeleteRefList, list, false, true)); + else + taskList->AddItem(NewFunctionObject(FSMoveToTrash, list, + (BList *)NULL, false)); + + if (selectNext && ViewMode() == kListMode) { + // next, if in list view mode try selecting the next item after + BPose *pose = fSelectionList->ItemAt(0); + + // find a point in the pose + BPoint pointInPose(kListOffset + 5, 5); + int32 index = IndexOfPose(pose); + pointInPose.y += fListElemHeight * index; + + TTracker *tracker = dynamic_cast(be_app); + + ASSERT(TargetModel()); + if (tracker) + // add a function object to the list of tasks to run + // that will select the next item after the one we just + // deleted + taskList->AddItem(NewMemberFunctionObject( + &TTracker::SelectPoseAtLocationSoon, tracker, + *TargetModel()->NodeRef(), pointInPose)); + + } + // execute the two tasks in order + ThreadSequence::Launch(taskList, true); +} + + +inline void +CopyOneTrashedRefAsEntry(const entry_ref *ref, BObjectList *trashList, + BObjectList *noTrashList, std::map *deviceHasTrash) +{ + std::map &deviceHasTrashTmp = *deviceHasTrash; + // work around stupid binding problems with EachListItem + + BDirectory entryDir(ref); + bool isVolume = entryDir.IsRootDirectory(); + // volumes will get unmounted + + // see if pose's device has a trash + int32 device = ref->device; + BDirectory trashDir; + + // cache up the result in a map so that we don't have to keep calling + // FSGetTrashDir over and over + if (!isVolume + && deviceHasTrashTmp.find(device) == deviceHasTrashTmp.end()) + deviceHasTrashTmp[device] = FSGetTrashDir(&trashDir, device) == B_OK; + + if (isVolume || deviceHasTrashTmp[device]) + trashList->AddItem(new entry_ref(*ref)); + else + noTrashList->AddItem(new entry_ref(*ref)); +} + + +static void +CopyPoseOneAsEntry(BPose *pose, BObjectList *trashList, + BObjectList *noTrashList, std::map *deviceHasTrash) +{ + CopyOneTrashedRefAsEntry(pose->TargetModel()->EntryRef(), trashList, + noTrashList, deviceHasTrash); +} + + +void +BPoseView::MoveSelectionOrEntryToTrash(const entry_ref *ref, bool selectNext) +{ + BObjectList *entriesToTrash = new + BObjectList(fSelectionList->CountItems()); + BObjectList *entriesToDeleteOnTheSpot = new + BObjectList(20, true); + std::map deviceHasTrash; + + if (ref) { + CopyOneTrashedRefAsEntry(ref, entriesToTrash, entriesToDeleteOnTheSpot, + &deviceHasTrash); + } else { + EachListItem(fSelectionList, CopyPoseOneAsEntry, entriesToTrash, + entriesToDeleteOnTheSpot, &deviceHasTrash); + } + + if (entriesToDeleteOnTheSpot->CountItems()) { + const char *alertText; + if (ref) { + alertText = "The selected item cannot be moved to the Trash. " + "Would you like to delete it instead? (This operation cannot " + "be reverted.)"; + } else { + alertText = "Some of the selected items cannot be moved to the Trash. " + "Would you like to delete them instead? (This operation cannot " + "be reverted.)"; + } + + if ((new BAlert("", alertText, "Cancel", "Delete"))->Go() == 0) + return; + } + + MoveListToTrash(entriesToTrash, selectNext, false); + MoveListToTrash(entriesToDeleteOnTheSpot, selectNext, true); +} + + +void +BPoseView::MoveSelectionToTrash(bool selectNext) +{ + if (fSelectionList->IsEmpty()) + return; + + // create entry_ref list from selection + // separate items that can be trashed from ones that cannot + + MoveSelectionOrEntryToTrash(0, selectNext); +} + + +void +BPoseView::MoveEntryToTrash(const entry_ref *ref, bool selectNext) +{ + MoveSelectionOrEntryToTrash(ref, selectNext); +} + + +void +BPoseView::DeleteSelection(bool selectNext, bool askUser) +{ + int32 count = fSelectionList -> CountItems(); + if (count <= 0) + return; + + BObjectList *entriesToDelete = new BObjectList(count, true); + + for (int32 index = 0; index < count; index++) + entriesToDelete->AddItem(new entry_ref((*fSelectionList->ItemAt(index) + ->TargetModel()->EntryRef()))); + + Delete(entriesToDelete, selectNext, askUser); +} + + +void +BPoseView::RestoreSelectionFromTrash(bool selectNext) +{ + int32 count = fSelectionList -> CountItems(); + if (count <= 0) + return; + + BObjectList *entriesToRestore = new BObjectList(count, true); + + for (int32 index = 0; index < count; index++) + entriesToRestore->AddItem(new entry_ref((*fSelectionList->ItemAt(index) + ->TargetModel()->EntryRef()))); + + RestoreItemsFromTrash(entriesToRestore, selectNext); +} + + +void +BPoseView::Delete(const entry_ref &ref, bool selectNext, bool askUser) +{ + BObjectList *entriesToDelete = new BObjectList(1, true); + entriesToDelete->AddItem(new entry_ref(ref)); + + Delete(entriesToDelete, selectNext, askUser); +} + + +void +BPoseView::Delete(BObjectList *list, bool selectNext, bool askUser) +{ + if (list->CountItems() == 0) { + delete list; + return; + } + + BObjectList *taskList = + new BObjectList(2, true); + + // first move selection to trash, + taskList->AddItem(NewFunctionObject(FSDeleteRefList, list, false, askUser)); + + if (selectNext && ViewMode() == kListMode) { + // next, if in list view mode try selecting the next item after + BPose *pose = fSelectionList->ItemAt(0); + + // find a point in the pose + BPoint pointInPose(kListOffset + 5, 5); + int32 index = IndexOfPose(pose); + pointInPose.y += fListElemHeight * index; + + TTracker *tracker = dynamic_cast(be_app); + + ASSERT(TargetModel()); + if (tracker) + // add a function object to the list of tasks to run + // that will select the next item after the one we just + // deleted + taskList->AddItem(NewMemberFunctionObject( + &TTracker::SelectPoseAtLocationSoon, tracker, + *TargetModel()->NodeRef(), pointInPose)); + + } + // execute the two tasks in order + ThreadSequence::Launch(taskList, true); +} + + +void +BPoseView::RestoreItemsFromTrash(BObjectList *list, bool selectNext) +{ + if (list->CountItems() == 0) { + delete list; + return; + } + + BObjectList *taskList = + new BObjectList(2, true); + + // first restoree selection + taskList->AddItem(NewFunctionObject(FSRestoreRefList, list, false)); + + if (selectNext && ViewMode() == kListMode) { + // next, if in list view mode try selecting the next item after + BPose *pose = fSelectionList->ItemAt(0); + + // find a point in the pose + BPoint pointInPose(kListOffset + 5, 5); + int32 index = IndexOfPose(pose); + pointInPose.y += fListElemHeight * index; + + TTracker *tracker = dynamic_cast(be_app); + + ASSERT(TargetModel()); + if (tracker) + // add a function object to the list of tasks to run + // that will select the next item after the one we just + // restored + taskList->AddItem(NewMemberFunctionObject( + &TTracker::SelectPoseAtLocationSoon, tracker, + *TargetModel()->NodeRef(), pointInPose)); + + } + // execute the two tasks in order + ThreadSequence::Launch(taskList, true); +} + + +void +BPoseView::SelectAll() +{ + BRect bounds(Bounds()); + + // clear selection list + fSelectionList->MakeEmpty(); + fMimeTypesInSelectionCache.MakeEmpty(); + fSelectionPivotPose = NULL; + fRealPivotPose = NULL; + + int32 startIndex = 0; + BPoint loc(0, 0); + + bool iconMode = ViewMode() != kListMode; + + int32 count = fPoseList->CountItems(); + for (int32 index = startIndex; index < count; index++) { + BPose *pose = fPoseList->ItemAt(index); + fSelectionList->AddItem(pose); + if (index == startIndex) + fSelectionPivotPose = pose; + + if (!pose->IsSelected()) { + pose->Select(true); + + BRect poseRect; + if (iconMode) + poseRect = pose->CalcRect(this); + else + poseRect = pose->CalcRect(loc, this); + + if (bounds.Intersects(poseRect)) { + pose->Draw(poseRect, this, false); + Flush(); + } + } + + loc.y += fListElemHeight; + } + + if (fSelectionChangedHook) + ContainerWindow()->SelectionChanged(); +} + + +void +BPoseView::InvertSelection() +{ + // Since this function shares most code with + // SelectAll(), we could make SelectAll() empty the selection, + // then call InvertSelection() + + BRect bounds(Bounds()); + + int32 startIndex = 0; + BPoint loc(0, 0); + + fMimeTypesInSelectionCache.MakeEmpty(); + fSelectionPivotPose = NULL; + fRealPivotPose = NULL; + + bool iconMode = ViewMode() != kListMode; + + int32 count = fPoseList->CountItems(); + for (int32 index = startIndex; index < count; index++) { + BPose *pose = fPoseList->ItemAt(index); + + if (pose->IsSelected()) { + fSelectionList->RemoveItem(pose); + pose->Select(false); + } else { + if (index == startIndex) + fSelectionPivotPose = pose; + fSelectionList->AddItem(pose); + pose->Select(true); + } + + BRect poseRect; + if (iconMode) + poseRect = pose->CalcRect(this); + else + poseRect = pose->CalcRect(loc, this); + + if (bounds.Intersects(poseRect)) + Invalidate(); + + loc.y += fListElemHeight; + } + + if (fSelectionChangedHook) + ContainerWindow()->SelectionChanged(); +} + + +int32 +BPoseView::SelectMatchingEntries(const BMessage *message) +{ + int32 matchCount = 0; + SetMultipleSelection(true); + + ClearSelection(); + + TrackerStringExpressionType expressionType; + BString expression; + const char *expressionPointer; + bool invertSelection; + bool ignoreCase; + + message->FindInt32("ExpressionType", (int32*)&expressionType); + message->FindString("Expression", &expressionPointer); + message->FindBool("InvertSelection", &invertSelection); + message->FindBool("IgnoreCase", &ignoreCase); + + expression = expressionPointer; + + int32 count = fPoseList->CountItems(); + TrackerString name; + + RegExp regExpression; + + // Make sure we don't have any errors in the expression + // before we match the names: + if (expressionType == kRegexpMatch) { + regExpression.SetTo(expression); + + if (regExpression.InitCheck() != B_OK) { + BString message; + message << "Error in regular expression:\n\n'"; + message << regExpression.ErrorString() << "'"; + (new BAlert("", message.String(), "OK", NULL, NULL, B_WIDTH_AS_USUAL, + B_STOP_ALERT))->Go(); + return 0; + } + } + + // There is room for optimizations here: If regexp-type match, the Matches() + // function compiles the expression for every entry. One could use + // TrackerString::CompileRegExp and reuse the expression. However, then we have + // to take care of the case sensitivity ourselves. + for (int32 index = 0; index < count; index++) { + BPose *pose = fPoseList->ItemAt(index); + name = pose->TargetModel()->Name(); + if (name.Matches(expression.String(), !ignoreCase, expressionType) ^ invertSelection) { + matchCount++; + AddPoseToSelection(pose, index); + } + } + + Window()->Activate(); + // Make sure the window is activated for + // subsequent manipulations. Esp. needed + // for the Desktop window. + + return matchCount; +} + + +void +BPoseView::ShowSelectionWindow() +{ + Window()->PostMessage(kShowSelectionWindow); +} + + +void +BPoseView::KeyDown(const char *bytes, int32 count) +{ + char key = bytes[0]; + + switch (key) { + case B_LEFT_ARROW: + case B_RIGHT_ARROW: + case B_UP_ARROW: + case B_DOWN_ARROW: + { + int32 index; + BPose *pose = FindNearbyPose(key, &index); + if (pose == NULL) + break; + + if (fMultipleSelection && modifiers() & B_SHIFT_KEY) { + if (pose->IsSelected()) { + RemovePoseFromSelection(fSelectionList->LastItem()); + fSelectionPivotPose = pose; + ScrollIntoView(pose, index, false); + } else + AddPoseToSelection(pose, index, true); + } else + SelectPose(pose, index); + break; + } + + case B_RETURN: + OpenSelection(); + break; + + case B_HOME: + // select the first entry (if in listview mode), and + // scroll to the top of the view + if (ViewMode() == kListMode) { + BPose *pose = fSelectionList->LastItem(); + + if (pose != NULL && fMultipleSelection && (modifiers() & B_SHIFT_KEY) != 0) { + int32 index = fPoseList->IndexOf(pose); + + // select all items from the current one till the top + for (int32 i = index; i-- > 0; ) { + pose = fPoseList->ItemAt(i); + if (pose == NULL) + continue; + + if (!pose->IsSelected()) + AddPoseToSelection(pose, i, i == 0); + } + } else + SelectPose(fPoseList->FirstItem(), 0); + } else if (fVScrollBar) + fVScrollBar->SetValue(0); + break; + + case B_END: + // select the last entry (if in listview mode), and + // scroll to the bottom of the view + if (ViewMode() == kListMode) { + BPose *pose = fSelectionList->FirstItem(); + + if (pose != NULL && fMultipleSelection && (modifiers() & B_SHIFT_KEY) != 0) { + int32 index = fPoseList->IndexOf(pose); + int32 count = fPoseList->CountItems() - 1; + + // select all items from the current one to the bottom + for (int32 i = index; i <= count; i++) { + pose = fPoseList->ItemAt(i); + if (pose == NULL) + continue; + + if (!pose->IsSelected()) + AddPoseToSelection(pose, i, i == count); + } + } else + SelectPose(fPoseList->LastItem(), fPoseList->CountItems() - 1); + } else if (fVScrollBar) { + float max, min; + fVScrollBar->GetRange(&min, &max); + fVScrollBar->SetValue(max); + } + break; + + case B_PAGE_UP: + if (fVScrollBar) { + float max, min; + fVScrollBar->GetSteps(&min, &max); + fVScrollBar->SetValue(fVScrollBar->Value() - max); + } + break; + + case B_PAGE_DOWN: + if (fVScrollBar) { + float max, min; + fVScrollBar->GetSteps(&min, &max); + fVScrollBar->SetValue(fVScrollBar->Value() + max); + } + break; + + case B_TAB: + if (IsFilePanel()) + _inherited::KeyDown(bytes, count); + else { + if (fSelectionList->IsEmpty()) + fMatchString[0] = '\0'; + else { + BPose *pose = fSelectionList->FirstItem(); + strncpy(fMatchString, pose->TargetModel()->Name(), B_FILE_NAME_LENGTH - 1); + fMatchString[B_FILE_NAME_LENGTH - 1] = '\0'; + } + + bool reverse = (Window()->CurrentMessage()->FindInt32("modifiers") + & B_SHIFT_KEY) != 0; + int32 index; + BPose *pose = FindNextMatch(&index, reverse); + if (!pose) { // wrap around + if (reverse) { + fMatchString[0] = (char)0xff; + fMatchString[1] = '\0'; + } else + fMatchString[0] = '\0'; + pose = FindNextMatch(&index, reverse); + } + + SelectPose(pose, index); + } + break; + + case B_DELETE: + { + // Make sure user can't trash something already in the trash. + BEntry entry(TargetModel()->EntryRef()); + if (FSIsTrashDir(&entry)) { + // Delete without asking from the trash + DeleteSelection(true, false); + } else { + TrackerSettings settings; + + if ((modifiers() & B_SHIFT_KEY) != 0 || settings.DontMoveFilesToTrash()) + DeleteSelection(true, settings.AskBeforeDeleteFile()); + else + MoveSelectionToTrash(); + } + break; + } + + case B_BACKSPACE: + // remove last char from the typeahead buffer + if (strcmp(fMatchString, "") != 0) { + fMatchString[strlen(fMatchString) - 1] = '\0'; + + fLastKeyTime = system_time(); + + fCountView->SetTypeAhead(fMatchString); + + // select our new string + int32 index; + BPose *pose = FindBestMatch(&index); + if (!pose) { // wrap around + fMatchString[0] = '\0'; + pose = FindBestMatch(&index); + } + SelectPose(pose, index); + } + break; + + default: + { + // handle typeahead selection + + // create a null-terminated version of typed char + char searchChar[4] = { key, 0 }; + + bigtime_t doubleClickSpeed; + get_click_speed(&doubleClickSpeed); + + // start watching + if (fKeyRunner == NULL) { + fKeyRunner = new BMessageRunner(this, new BMessage(kCheckTypeahead), doubleClickSpeed); + if (fKeyRunner->InitCheck() != B_OK) + return; + } + + // add char to existing matchString or start new match string + // make sure we don't overfill matchstring + if (system_time() - fLastKeyTime < (doubleClickSpeed * 2)) { + uint32 nchars = B_FILE_NAME_LENGTH - strlen(fMatchString); + strncat(fMatchString, searchChar, nchars); + } else { + strncpy(fMatchString, searchChar, B_FILE_NAME_LENGTH - 1); + } + fMatchString[B_FILE_NAME_LENGTH - 1] = '\0'; + fLastKeyTime = system_time(); + + fCountView->SetTypeAhead(fMatchString); + + int32 index; + BPose *pose = FindBestMatch(&index); + if (!pose) { // wrap around + fMatchString[0] = '\0'; + pose = FindBestMatch(&index); + } + SelectPose(pose, index); + break; + } + } +} + + +BPose * +BPoseView::FindNextMatch(int32 *matchingIndex, bool reverse) +{ + char bestSoFar[B_FILE_NAME_LENGTH] = { 0 }; + BPose *poseToSelect = NULL; + + // loop through all poses to find match + int32 count = fPoseList->CountItems(); + for (int32 index = 0; index < count; index++) { + BPose *pose = fPoseList->ItemAt(index); + + if (reverse) { + if (strcasecmp(pose->TargetModel()->Name(), fMatchString) < 0) + if (strcasecmp(pose->TargetModel()->Name(), bestSoFar) >= 0 + || !bestSoFar[0]) { + strcpy(bestSoFar, pose->TargetModel()->Name()); + poseToSelect = pose; + *matchingIndex = index; + } + } else if (strcasecmp(pose->TargetModel()->Name(), fMatchString) > 0) + if (strcasecmp(pose->TargetModel()->Name(), bestSoFar) <= 0 + || !bestSoFar[0]) { + strcpy(bestSoFar, pose->TargetModel()->Name()); + poseToSelect = pose; + *matchingIndex = index; + } + + } + + return poseToSelect; +} + + +BPose * +BPoseView::FindBestMatch(int32 *index) +{ + char bestSoFar[B_FILE_NAME_LENGTH] = { 0 }; + BPose *poseToSelect = NULL; + + BColumn *firstColumn = FirstColumn(); + + // loop through all poses to find match + int32 count = fPoseList->CountItems(); + for (int32 i = 0; i < count; i++) { + BPose *pose = fPoseList->ItemAt(i); + const char * text; + if (ViewMode() == kListMode) + text = pose->TargetModel()->Name(); + else { + ModelNodeLazyOpener modelOpener(pose->TargetModel()); + BTextWidget *widget = pose->WidgetFor(firstColumn, this, modelOpener); + if (widget) + text = widget->Text(); + else + text = pose->TargetModel()->Name(); + } + + if (strcasecmp(text, fMatchString) >= 0) + if (strcasecmp(text, bestSoFar) <= 0 || !bestSoFar[0]) { + strcpy(bestSoFar, text); + poseToSelect = pose; + *index = i; + } + } + + return poseToSelect; +} + + +static bool +LinesIntersect(float s1, float e1, float s2, float e2) +{ + return std::max(s1, s2) < std::min(e1, e2); +} + + +BPose * +BPoseView::FindNearbyPose(char arrowKey, int32 *poseIndex) +{ + int32 resultingIndex = -1; + BPose *poseToSelect = NULL; + BPose *selectedPose = fSelectionList->LastItem(); + + if (ViewMode() == kListMode) { + switch (arrowKey) { + case B_UP_ARROW: + case B_LEFT_ARROW: + if (selectedPose) { + resultingIndex = fPoseList->IndexOf(selectedPose) - 1; + poseToSelect = fPoseList->ItemAt(resultingIndex); + if (!poseToSelect && arrowKey == B_LEFT_ARROW) { + resultingIndex = fPoseList->CountItems() - 1; + poseToSelect = fPoseList->LastItem(); + } + } else { + resultingIndex = fPoseList->CountItems() - 1; + poseToSelect = fPoseList->LastItem(); + } + break; + + case B_DOWN_ARROW: + case B_RIGHT_ARROW: + if (selectedPose) { + resultingIndex = fPoseList->IndexOf(selectedPose) + 1; + poseToSelect = fPoseList->ItemAt(resultingIndex); + if (!poseToSelect && arrowKey == B_RIGHT_ARROW) { + resultingIndex = 0; + poseToSelect = fPoseList->FirstItem(); + } + } else { + resultingIndex = 0; + poseToSelect = fPoseList->FirstItem(); + } + break; + } + *poseIndex = resultingIndex; + return poseToSelect; + } + + // must be in one of the icon modes + + // handle case where there is no current selection + if (fSelectionList->IsEmpty()) { + // find the upper-left pose (I know it's ugly!) + poseToSelect = fVSPoseList->FirstItem(); + for (int32 index = 0; ;index++) { + BPose *pose = fVSPoseList->ItemAt(++index); + if (!pose) + break; + + BRect selectedBounds(poseToSelect->CalcRect(this)); + BRect poseRect(pose->CalcRect(this)); + + if (poseRect.top > selectedBounds.top) + break; + + if (poseRect.left < selectedBounds.left) + poseToSelect = pose; + } + + return poseToSelect; + } + + BRect selectionRect(selectedPose->CalcRect(this)); + BRect bestRect; + + // we're not in list mode so scan visually for pose to select + int32 count = fPoseList->CountItems(); + for (int32 index = 0; index < count; index++) { + BPose *pose = fPoseList->ItemAt(index); + BRect poseRect(pose->CalcRect(this)); + + switch (arrowKey) { + case B_LEFT_ARROW: + if (LinesIntersect(poseRect.top, poseRect.bottom, + selectionRect.top, selectionRect.bottom)) + if (poseRect.left < selectionRect.left) + if (poseRect.left > bestRect.left + || !bestRect.IsValid()) { + bestRect = poseRect; + poseToSelect = pose; + } + break; + + case B_RIGHT_ARROW: + if (LinesIntersect(poseRect.top, poseRect.bottom, + selectionRect.top, selectionRect.bottom)) + if (poseRect.right > selectionRect.right) + if (poseRect.right < bestRect.right + || !bestRect.IsValid()) { + bestRect = poseRect; + poseToSelect = pose; + } + break; + + case B_UP_ARROW: + if (LinesIntersect(poseRect.left, poseRect.right, + selectionRect.left, selectionRect.right)) + if (poseRect.top < selectionRect.top) + if (poseRect.top > bestRect.top + || !bestRect.IsValid()) { + bestRect = poseRect; + poseToSelect = pose; + } + break; + + case B_DOWN_ARROW: + if (LinesIntersect(poseRect.left, poseRect.right, + selectionRect.left, selectionRect.right)) + if (poseRect.bottom > selectionRect.bottom) + if (poseRect.bottom < bestRect.bottom + || !bestRect.IsValid()) { + bestRect = poseRect; + poseToSelect = pose; + } + break; + } + } + + if (poseToSelect) + return poseToSelect; + + return selectedPose; +} + + +void +BPoseView::ShowContextMenu(BPoint where) +{ + BContainerWindow *window = ContainerWindow(); + if (!window) + return; + + // handle pose selection + int32 index; + BPose *pose = FindPose(where, &index); + if (pose) { + if (!pose->IsSelected()) { + ClearSelection(); + pose->Select(true); + fSelectionList->AddItem(pose); + DrawPose(pose, index, false); + } + } else + ClearSelection(); + + window->Activate(); + window->UpdateIfNeeded(); + window->ShowContextMenu(where, pose ? pose->TargetModel()->EntryRef() : 0, this); + + if (fSelectionChangedHook) + window->SelectionChanged(); +} + + +void +BPoseView::MouseDown(BPoint where) +{ + // ToDo: + // add asynch mouse tracking + // + // handle disposing of drag data lazily + DragStop(); + BContainerWindow *window = ContainerWindow(); + if (!window) + return; + + if (IsDesktopWindow()) { + BScreen screen(Window()); + rgb_color color = screen.DesktopColor(); + SetLowColor(color); + SetViewColor(color); + } + + MakeFocus(); + + // "right" mouse button handling for context-sensitive menus + uint32 buttons = (uint32)window->CurrentMessage()->FindInt32("buttons"); + uint32 modifs = modifiers(); + + bool showContext = true; + if ((buttons & B_SECONDARY_MOUSE_BUTTON) == 0) + showContext = (modifs & B_CONTROL_KEY) != 0; + + // if a pose was hit, delay context menu for a bit to see if user dragged + if (showContext) { + int32 index; + BPose *pose = FindPose(where, &index); + if (!pose) { + ShowContextMenu(where); + return; + } + if (!pose->IsSelected()) { + ClearSelection(); + pose->Select(true); + fSelectionList->AddItem(pose); + DrawPose(pose, index, false); + } + + bigtime_t clickTime = system_time(); + BPoint loc; + GetMouse(&loc, &buttons); + for (;;) { + if (fabs(loc.x - where.x) > 4 || fabs(loc.y - where.y) > 4) + // moved the mouse, cancel showing the context menu + break; + + if (!buttons || (system_time() - clickTime) > 200000) { + // let go of button or pressing for a while, show menu now + ShowContextMenu(where); + return; + } + + snooze(10000); + GetMouse(&loc, &buttons); + } + } + + bool extendSelection = (modifs & B_SHIFT_KEY) && fMultipleSelection; + + CommitActivePose(); + + // see if mouse down occurred within a pose + int32 index; + BPose *pose = FindPose(where, &index); + if (pose) { + AddRemoveSelectionRange(where, extendSelection, pose); + + switch (WaitForMouseUpOrDrag(where)) { + case kWasDragged: + DragSelectedPoses(pose, where); + break; + + case kNotDragged: + if (!extendSelection && WasDoubleClick(pose, where)) { + // special handling for Path field double-clicks + if (!WasClickInPath(pose, index, where)) + OpenSelection(pose, &index); + + } else if (fAllowPoseEditing) + // mouse is up but no drag or double-click occurred + pose->MouseUp(BPoint(0, index * fListElemHeight), this, where, index); + + break; + + default: + // this is the CONTEXT_MENU case + break; + } + } else { + // click was not in any pose + fLastClickedPose = NULL; + + window->Activate(); + window->UpdateIfNeeded(); + DragSelectionRect(where, extendSelection); + } + + if (fSelectionChangedHook) + window->SelectionChanged(); +} + + +bool +BPoseView::WasClickInPath(const BPose *pose, int32 index, BPoint mouseLoc) const +{ + if (!pose || (ViewMode() != kListMode)) + return false; + + BPoint loc(0, index * fListElemHeight); + BTextWidget *widget; + if (!pose->PointInPose(loc, this, mouseLoc, &widget) || !widget) + return false; + + // note: the following code is wrong, because this sort of hashing + // may overlap and we get aliasing + if (widget->AttrHash() != AttrHashString(kAttrPath, B_STRING_TYPE)) + return false; + + BEntry entry(widget->Text()); + if (entry.InitCheck() != B_OK) + return false; + + entry_ref ref; + if (entry.GetRef(&ref) == B_OK) { + BMessage message(B_REFS_RECEIVED); + message.AddRef("refs", &ref); + be_app->PostMessage(&message); + return true; + } + + return false; +} + + +bool +BPoseView::WasDoubleClick(const BPose *pose, BPoint point) +{ + // check time and proximity + BPoint delta = point - fLastClickPt; + + bigtime_t sysTime; + Window()->CurrentMessage()->FindInt64("when", &sysTime); + + bigtime_t timeDelta = sysTime - fLastClickTime; + + bigtime_t doubleClickSpeed; + get_click_speed(&doubleClickSpeed); + + if (timeDelta < doubleClickSpeed + && fabs(delta.x) < kDoubleClickTresh + && fabs(delta.y) < kDoubleClickTresh + && pose == fLastClickedPose) { + fLastClickPt.Set(LONG_MAX, LONG_MAX); + fLastClickedPose = NULL; + fLastClickTime = 0; + return true; + } + + fLastClickPt = point; + fLastClickedPose = pose; + fLastClickTime = sysTime; + return false; +} + + +static void +AddPoseRefToMessage(BPose *, Model *model, BMessage *message) +{ + // Make sure that every file added to the message has its + // MIME type set. + BNode node(model->EntryRef()); + if (node.InitCheck() == B_OK) { + BNodeInfo info(&node); + char type[B_MIME_TYPE_LENGTH]; + type[0] = '\0'; + if (info.GetType(type) != B_OK) { + BPath path(model->EntryRef()); + if (path.InitCheck() == B_OK) + update_mime_info(path.Path(), false, false, false); + } + } + message->AddRef("refs", model->EntryRef()); +} + + +void +BPoseView::DragSelectedPoses(const BPose *pose, BPoint clickPoint) +{ + if (!fDragEnabled) + return; + + ASSERT(pose); + + // make sure pose is selected, it could have been deselected as part of + // a click during selection extention + if (!pose->IsSelected()) + return; + + // setup tracking rect by unioning all selected pose rects + BMessage message(B_SIMPLE_DATA); + message.AddPointer("src_window", Window()); + message.AddPoint("click_pt", clickPoint); + + // add Tracker token so that refs received recipients can script us + message.AddMessenger("TrackerViewToken", BMessenger(this)); + + EachPoseAndModel(fSelectionList, &AddPoseRefToMessage, &message); + + // do any special drag&drop handling + if (fSelectionList->CountItems() == 1) { + // for now just recognize text clipping files + + BFile file(fSelectionList->ItemAt(0)->TargetModel()->EntryRef(), O_RDONLY); + if (file.InitCheck() == B_OK) { + BNodeInfo info(&file); + char type[B_MIME_TYPE_LENGTH]; + type[0] = '\0'; + + info.GetType(type); + + int32 tmp; + if (strcasecmp(type, kPlainTextMimeType) == 0 + // got a text file + && file.ReadAttr(kAttrClippingFile, B_RAW_TYPE, 0, + &tmp, sizeof(int32)) == sizeof(int32)) { + // and a clipping file + + file.Seek(0, SEEK_SET); + off_t size = 0; + file.GetSize(&size); + if (size) { + char *buffer = new char[size]; + if (file.Read(buffer, (size_t)size) == size) { + message.AddData(kPlainTextMimeType, B_MIME_TYPE, buffer, (ssize_t)size); + // add text into drag message + + attr_info attrInfo; + if (file.GetAttrInfo("styles", &attrInfo) == B_OK + && attrInfo.size > 0) { + char *data = new char [attrInfo.size]; + file.ReadAttr("styles", B_RAW_TYPE, 0, data, (size_t)attrInfo.size); + int32 textRunSize; + text_run_array *textRuns = BTextView::UnflattenRunArray(data, + &textRunSize); + delete [] data; + message.AddData("application/x-vnd.Be-text_run_array", + B_MIME_TYPE, textRuns, textRunSize); + free(textRuns); + } + } + delete [] buffer; + } + } else if (strcasecmp(type, kBitmapMimeType) == 0 + // got a text file + && file.ReadAttr(kAttrClippingFile, B_RAW_TYPE, 0, + &tmp, sizeof(int32)) == sizeof(int32)) { + file.Seek(0, SEEK_SET); + off_t size = 0; + file.GetSize(&size); + if (size) { + char *buffer = new char[size]; + if (file.Read(buffer, (size_t)size) == size) { + BMessage embeddedBitmap; + if (embeddedBitmap.Unflatten(buffer) == B_OK) + message.AddMessage(kBitmapMimeType, &embeddedBitmap); + // add bitmap into drag message + } + delete [] buffer; + } + } + } + } + + // make sure button is still down + uint32 button; + BPoint tempLoc; + GetMouse(&tempLoc, &button); + if (button) { + int32 index = fPoseList->IndexOf(pose); + message.AddInt32("buttons", (int32)button); + BRect dragRect(GetDragRect(index)); + BBitmap *dragBitmap = NULL; + BPoint offset; + + // The bitmap is now always created (if DRAG_FRAME is not defined) + +#ifdef DRAG_FRAME + if (dragRect.Width() < kTransparentDragThreshold.x + && dragRect.Height() < kTransparentDragThreshold.y) +#endif + dragBitmap = MakeDragBitmap(dragRect, clickPoint, index, offset); + + if (dragBitmap) { + DragMessage(&message, dragBitmap, B_OP_ALPHA, offset); + // this DragMessage supports alpha blending + } else + DragMessage(&message, dragRect); + + // turn on auto scrolling + fAutoScrollState = kWaitForTransition; + Window()->SetPulseRate(100000); + } +} + + +BBitmap * +BPoseView::MakeDragBitmap(BRect dragRect, BPoint clickedPoint, int32 clickedPoseIndex, BPoint &offset) +{ + BRect inner(clickedPoint.x - kTransparentDragThreshold.x / 2, + clickedPoint.y - kTransparentDragThreshold.x / 2, + clickedPoint.x + kTransparentDragThreshold.x / 2, + clickedPoint.y + kTransparentDragThreshold.x / 2); + + // (BRect & BRect) doesn't work correctly if the rectangles don't intersect + // this catches a bug that is produced somewhere before this function is called + if (inner.right < dragRect.left || inner.bottom < dragRect.top + || inner.left > dragRect.right || inner.top > dragRect.bottom) + return NULL; + + inner = inner & dragRect; + + // If the selection is bigger than the specified limit, the + // contents will fade out when they come near the borders + bool fadeTop = false, fadeBottom = false, fadeLeft = false, fadeRight = false, fade = false; + if (inner.left > dragRect.left) { + inner.left = max(inner.left - 32, dragRect.left); + fade = fadeLeft = true; + } + if (inner.right < dragRect.right) { + inner.right = min(inner.right + 32, dragRect.right); + fade = fadeRight = true; + } + if (inner.top > dragRect.top) { + inner.top = max(inner.top - 32, dragRect.top); + fade = fadeTop = true; + } + if (inner.bottom < dragRect.bottom) { + inner.bottom = min(inner.bottom + 32, dragRect.bottom); + fade = fadeBottom = true; + } + + // set the offset for the dragged bitmap (for the BView::DragMessage() call) + offset = clickedPoint - inner.LeftTop(); + + BRect rect(inner); + rect.OffsetTo(B_ORIGIN); + + BBitmap *bitmap = new BBitmap(rect, B_RGBA32, true); + bitmap->Lock(); + BView *view = new BView(bitmap->Bounds(), "", B_FOLLOW_NONE, 0); + bitmap->AddChild(view); + + view->SetOrigin(0, 0); + + BRect clipRect(view->Bounds()); + BRegion newClip; + newClip.Set(clipRect); + view->ConstrainClippingRegion(&newClip); + + // Transparent draw magic + view->SetHighColor(0, 0, 0, uint8(fade ? 10 : 0)); + view->FillRect(view->Bounds()); + view->Sync(); + + if (fade) { + // If we fade out any border of the selection, the background + // will be slightly darker, and we will also fade out the + // edges so that everything looks smooth + uint32 *bits = (uint32 *)bitmap->Bits(); + int32 width = bitmap->BytesPerRow() / 4; + + FadeRGBA32Horizontal(bits, width, int32(rect.bottom), + int32(rect.right), int32(rect.right) - 16); + FadeRGBA32Horizontal(bits, width, int32(rect.bottom), 0, 16); + + FadeRGBA32Vertical(bits, width, int32(rect.bottom), + int32(rect.bottom), int32(rect.bottom) - 16); + FadeRGBA32Vertical(bits, width, int32(rect.bottom), 0, 16); + } + + view->SetDrawingMode(B_OP_ALPHA); + view->SetHighColor(0, 0, 0, uint8(fade ? 164 : 128)); + // set the level of transparency by value + view->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_COMPOSITE); + + BRect bounds(Bounds()); + + BPose *pose = fPoseList->ItemAt(clickedPoseIndex); + if (ViewMode() == kListMode) { + int32 count = fPoseList->CountItems(); + int32 startIndex = (int32)(bounds.top / fListElemHeight); + BPoint loc(0, startIndex * fListElemHeight); + + for (int32 index = startIndex; index < count; index++) { + pose = fPoseList->ItemAt(index); + if (pose->IsSelected()) { + BRect poseRect(pose->CalcRect(loc, this, true)); + if (poseRect.Intersects(inner)) { + BPoint offsetBy(-inner.LeftTop().x, -inner.LeftTop().y); + pose->Draw(poseRect, this, view, true, 0, offsetBy, false); + } + } + loc.y += fListElemHeight; + if (loc.y > bounds.bottom) + break; + } + } else { + // add rects for visible poses only (uses VSList!!) + int32 startIndex = FirstIndexAtOrBelow((int32)(bounds.top - IconPoseHeight())); + int32 count = fVSPoseList->CountItems(); + + for (int32 index = startIndex; index < count; index++) { + pose = fVSPoseList->ItemAt(index); + if (pose && pose->IsSelected()) { + BRect poseRect(pose->CalcRect(this)); + if (!poseRect.Intersects(inner)) + continue; + + BPoint offsetBy(-inner.LeftTop().x, -inner.LeftTop().y); + pose->Draw(poseRect, this, view, true, 0, offsetBy, false); + } + } + } + + view->Sync(); + + // Fade out the contents if necessary + if (fade) { + uint32 *bits = (uint32 *)bitmap->Bits(); + int32 width = bitmap->BytesPerRow() / 4; + + if (fadeLeft) + FadeRGBA32Horizontal(bits, width, int32(rect.bottom), 0, 64); + if (fadeRight) + FadeRGBA32Horizontal(bits, width, int32(rect.bottom), + int32(rect.right), int32(rect.right) - 64); + + if (fadeTop) + FadeRGBA32Vertical(bits, width, int32(rect.bottom), 0, 64); + if (fadeBottom) + FadeRGBA32Vertical(bits, width, int32(rect.bottom), + int32(rect.bottom), int32(rect.bottom) - 64); + } + + bitmap->Unlock(); + return bitmap; +} + + +BRect +BPoseView::GetDragRect(int32 clickedPoseIndex) +{ + BRect result; + BRect bounds(Bounds()); + + BPose *pose = fPoseList->ItemAt(clickedPoseIndex); + if (ViewMode() == kListMode) { + // get starting rect of clicked pose + result = CalcPoseRect(pose, clickedPoseIndex, true); + + // add rects for visible poses only + int32 count = fPoseList->CountItems(); + int32 startIndex = (int32)(bounds.top / fListElemHeight); + BPoint loc(0, startIndex * fListElemHeight); + + for (int32 index = startIndex; index < count; index++) { + pose = fPoseList->ItemAt(index); + if (pose->IsSelected()) + result = result | pose->CalcRect(loc, this, true); + + loc.y += fListElemHeight; + if (loc.y > bounds.bottom) + break; + } + } else { + // get starting rect of clicked pose + result = pose->CalcRect(this); + + // add rects for visible poses only (uses VSList!!) + int32 count = fVSPoseList->CountItems(); + for (int32 index = FirstIndexAtOrBelow((int32)(bounds.top - IconPoseHeight())); + index < count; index++) { + BPose *pose = fVSPoseList->ItemAt(index); + if (pose) { + if (pose->IsSelected()) + result = result | pose->CalcRect(this); + + if (pose->Location().y > bounds.bottom) + break; + } + } + } + + return result; +} + + +static void +AddIfPoseSelected(BPose *pose, PoseList *list) +{ + if (pose->IsSelected()) + list->AddItem(pose); +} + + +void +BPoseView::DragSelectionRect(BPoint startPoint, bool shouldExtend) +{ + // only clear selection if we are not extending it + if (!shouldExtend) + ClearSelection(); + + if (WaitForMouseUpOrDrag(startPoint) != kWasDragged) { + if (!shouldExtend) + ClearSelection(); + return; + } + + if (!fSelectionRectEnabled || !fMultipleSelection) { + ClearSelection(); + return; + } + + // clearing the selection could take a while so poll the mouse again + BPoint newMousePoint; + uint32 button; + GetMouse(&newMousePoint, &button); + + // draw initial empty selection rectangle + BRect lastselectionRect; + fSelectionRect = lastselectionRect = BRect(startPoint, startPoint - BPoint(1, 1)); + + if (!fTransparentSelection) { + SetDrawingMode(B_OP_INVERT); + StrokeRect(fSelectionRect, B_MIXED_COLORS); + SetDrawingMode(B_OP_OVER); + } + + BList *selectionList = new BList; + + BPoint oldMousePoint(startPoint); + while (button) { + GetMouse(&newMousePoint, &button); + if (newMousePoint != oldMousePoint) { + oldMousePoint = newMousePoint; + BRect oldRect = fSelectionRect; + fSelectionRect.top = std::min(newMousePoint.y, startPoint.y); + fSelectionRect.left = std::min(newMousePoint.x, startPoint.x); + fSelectionRect.bottom = std::max(newMousePoint.y, startPoint.y); + fSelectionRect.right = std::max(newMousePoint.x, startPoint.x); + + // erase old rect + if (!fTransparentSelection) { + SetDrawingMode(B_OP_INVERT); + StrokeRect(oldRect, B_MIXED_COLORS); + SetDrawingMode(B_OP_OVER); + } + + fIsDrawingSelectionRect = true; + + CheckAutoScroll(newMousePoint, true, true); + + // use current selection rectangle to scan poses + if (ViewMode() == kListMode) + SelectPosesListMode(fSelectionRect, &selectionList); + else + SelectPosesIconMode(fSelectionRect, &selectionList); + + Window()->UpdateIfNeeded(); + + // draw new selected rect + if (!fTransparentSelection) { + SetDrawingMode(B_OP_INVERT); + StrokeRect(fSelectionRect, B_MIXED_COLORS); + SetDrawingMode(B_OP_OVER); + } else { + BRegion updateRegion1; + BRegion updateRegion2; + + bool samewidth = fSelectionRect.Width() == lastselectionRect.Width(); + bool sameheight = fSelectionRect.Height() == lastselectionRect.Height(); + + updateRegion1.Include(fSelectionRect); + updateRegion1.Exclude(lastselectionRect.InsetByCopy(samewidth ? 0 : 1, sameheight ? 0 : 1)); + updateRegion2.Include(lastselectionRect); + updateRegion2.Exclude(fSelectionRect.InsetByCopy(samewidth ? 0 : 1, sameheight ? 0 : 1)); + updateRegion1.Include(&updateRegion2); + BRect unionRect = fSelectionRect & lastselectionRect; + updateRegion1.Exclude(unionRect & BRect(-2000, startPoint.y, 2000, startPoint.y)); + updateRegion1.Exclude(unionRect & BRect(startPoint.x, -2000, startPoint.x, 2000)); + + lastselectionRect = fSelectionRect; + + Invalidate(&updateRegion1); + Window()->UpdateIfNeeded(); + } + + Flush(); + } + + snooze(20000); + } + + delete selectionList; + + fIsDrawingSelectionRect = false; + + // do final erase of selection rect + if (!fTransparentSelection) { + SetDrawingMode(B_OP_INVERT); + StrokeRect(fSelectionRect, B_MIXED_COLORS); + SetDrawingMode(B_OP_COPY); + } else { + Invalidate(fSelectionRect); + fSelectionRect.Set(0, 0, -1, -1); + Window()->UpdateIfNeeded(); + } + + // we now need to update the pose view's selection list by clearing it + // and then polling each pose for selection state and rebuilding list + fSelectionList->MakeEmpty(); + fMimeTypesInSelectionCache.MakeEmpty(); + + EachListItem(fPoseList, AddIfPoseSelected, fSelectionList); + + // and now make sure that the pivot point is in sync + if (fSelectionPivotPose && !fSelectionList->HasItem(fSelectionPivotPose)) + fSelectionPivotPose = NULL; + if (fRealPivotPose && !fSelectionList->HasItem(fRealPivotPose)) + fRealPivotPose = NULL; +} + +// ToDo: +// SelectPosesListMode and SelectPosesIconMode are terrible and share most code + +void +BPoseView::SelectPosesListMode(BRect selectionRect, BList **oldList) +{ + ASSERT(ViewMode() == kListMode); + + // collect all the poses which are enclosed inside the selection rect + BList *newList = new BList; + BRect bounds(Bounds()); + SetDrawingMode(B_OP_COPY); + + int32 startIndex = (int32)(selectionRect.top / fListElemHeight); + if (startIndex < 0) + startIndex = 0; + + BPoint loc(0, startIndex * fListElemHeight); + + int32 count = fPoseList->CountItems(); + for (int32 index = startIndex; index < count; index++) { + BPose *pose = fPoseList->ItemAt(index); + BRect poseRect(pose->CalcRect(loc, this)); + + if (selectionRect.Intersects(poseRect)) { + bool selected = pose->IsSelected(); + pose->Select(!fSelectionList->HasItem(pose)); + newList->AddItem((void *)index); // this sucks, need to clean up + // using a vector class instead of BList + + if ((selected != pose->IsSelected()) && poseRect.Intersects(bounds)) + pose->Draw(poseRect, this, false); + + // First Pose selected gets to be the pivot. + if ((fSelectionPivotPose == NULL) && (selected == false)) + fSelectionPivotPose = pose; + } + + loc.y += fListElemHeight; + if (loc.y > selectionRect.bottom) + break; + } + + // take the old set of enclosed poses and invert selection state + // on those which are no longer enclosed + count = (*oldList)->CountItems(); + for (int32 index = 0; index < count; index++) { + int32 oldIndex = (int32)(*oldList)->ItemAt(index); + + if (!newList->HasItem((void *)oldIndex)) { + BPose *pose = fPoseList->ItemAt(oldIndex); + pose->Select(!pose->IsSelected()); + loc.Set(0, oldIndex * fListElemHeight); + BRect poseRect(pose->CalcRect(loc, this)); + + if (poseRect.Intersects(bounds)) + pose->Draw(poseRect, this, false); + } + } + + delete *oldList; + *oldList = newList; +} + + +void +BPoseView::SelectPosesIconMode(BRect selectionRect, BList **oldList) +{ + ASSERT(ViewMode() != kListMode); + + // collect all the poses which are enclosed inside the selection rect + BList *newList = new BList; + BRect bounds(Bounds()); + SetDrawingMode(B_OP_COPY); + + int32 startIndex = FirstIndexAtOrBelow((int32)(selectionRect.top - IconPoseHeight()), true); + if (startIndex < 0) + startIndex = 0; + + int32 count = fPoseList->CountItems(); + for (int32 index = startIndex; index < count; index++) { + BPose *pose = fVSPoseList->ItemAt(index); + if (pose) { + BRect poseRect(pose->CalcRect(this)); + + if (selectionRect.Intersects(poseRect)) { + bool selected = pose->IsSelected(); + pose->Select(!fSelectionList->HasItem(pose)); + newList->AddItem((void *)index); + + if ((selected != pose->IsSelected()) && poseRect.Intersects(bounds)) + if (pose->IsSelected() || EraseWidgetTextBackground()) + pose->Draw(poseRect, this, false); + else + Invalidate(poseRect); + + // First Pose selected gets to be the pivot. + if ((fSelectionPivotPose == NULL) && (selected == false)) + fSelectionPivotPose = pose; + } + + if (pose->Location().y > selectionRect.bottom) + break; + } + } + + // take the old set of enclosed poses and invert selection state + // on those which are no longer enclosed + count = (*oldList)->CountItems(); + for (int32 index = 0; index < count; index++) { + int32 oldIndex = (int32)(*oldList)->ItemAt(index); + + if (!newList->HasItem((void *)oldIndex)) { + BPose *pose = fVSPoseList->ItemAt(oldIndex); + pose->Select(!pose->IsSelected()); + BRect poseRect(pose->CalcRect(this)); + + if (poseRect.Intersects(bounds)) { + if (pose->IsSelected() || EraseWidgetTextBackground()) + pose->Draw(poseRect, this, false); + else + Invalidate(poseRect); + } + } + } + + delete *oldList; + *oldList = newList; +} + + +void +BPoseView::AddRemoveSelectionRange(BPoint where, bool extendSelection, BPose *pose) +{ + ASSERT(pose); + + if ((pose == fSelectionPivotPose) && !extendSelection) + return; + + if ((modifiers() & B_COMMAND_KEY) && fSelectionPivotPose) { + // Multi Pose extend/shrink current selection + bool select = !pose->IsSelected() || !extendSelection; + // This weird bit of logic causes the selection to always + // center around the pivot point, unless you choose to hold + // down SHIFT, which will unselect between the pivot and + // the most recently selected Pose. + + if (!extendSelection) { + // Remember fSelectionPivotPose because ClearSelection() NULLs it + // and we need it to be preserved. + const BPose *savedPivotPose = fSelectionPivotPose; + ClearSelection(); + fSelectionPivotPose = savedPivotPose; + } + + if (ViewMode() == kListMode) { + int32 currSelIndex = fPoseList->IndexOf(pose); + int32 lastSelIndex = fPoseList->IndexOf(fSelectionPivotPose); + + int32 startRange; + int32 endRange; + + if (lastSelIndex < currSelIndex) { + startRange = lastSelIndex; + endRange = currSelIndex; + } else { + startRange = currSelIndex; + endRange = lastSelIndex; + } + + for (int32 i = startRange; i <= endRange; i++) + AddRemovePoseFromSelection(fPoseList->ItemAt(i), i, select); + + } else { + BRect selection(where, fSelectionPivotPose->Location()); + + // Things will get odd if we don't 'fix' the selection rect. + if (selection.left > selection.right) { + float temp = selection.right; + selection.right = selection.left; + selection.left = temp; + } + + if (selection.top > selection.bottom) { + float temp = selection.top; + selection.top = selection.bottom; + selection.bottom = temp; + } + + // If the selection rect is not at least 1 pixel high/wide, things + // are also not going to work out. + if (selection.IntegerWidth() < 1) + selection.right = selection.left + 1.0f; + + if (selection.IntegerHeight() < 1) + selection.bottom = selection.top + 1.0f; + + ASSERT(selection.IsValid()); + + int32 count = fPoseList->CountItems(); + for (int32 index = count - 1; index >= 0; index--) { + BPose *currPose = fPoseList->ItemAt(index); + if (selection.Intersects(currPose->CalcRect(this))) + AddRemovePoseFromSelection(currPose, index, select); + } + } + } else { + int32 index = fPoseList->IndexOf(pose); + if (!extendSelection) { + if (!pose->IsSelected()) { + // create new selection + ClearSelection(); + AddRemovePoseFromSelection(pose, index, true); + fSelectionPivotPose = pose; + } + } else { + fMimeTypesInSelectionCache.MakeEmpty(); + AddRemovePoseFromSelection(pose, index, !pose->IsSelected()); + } + } + + // If the list is empty, there cannot be a pivot pose, + // however if the list is not empty there must be a pivot + // pose. + if (fSelectionList->IsEmpty()) { + fSelectionPivotPose = NULL; + fRealPivotPose = NULL; + } else if (fSelectionPivotPose == NULL) { + fSelectionPivotPose = pose; + fRealPivotPose = pose; + } +} + + +int32 +BPoseView::WaitForMouseUpOrDrag(BPoint start) +{ + bigtime_t start_time = system_time(); + bigtime_t doubleClickSpeed; + get_click_speed(&doubleClickSpeed); + + // use double the doubleClickSpeed as a treshold + doubleClickSpeed *= 2; + + // loop until mouse has been dragged at least 2 pixels + uint32 button; + BPoint loc; + GetMouse(&loc, &button, false); + + while (button) { + GetMouse(&loc, &button, false); + if (fabs(loc.x - start.x) > 2 || fabs(loc.y - start.y) > 2) + return kWasDragged; + + if ((system_time() - start_time) > doubleClickSpeed) { + ShowContextMenu(start); + return kContextMenuShown; + } + + snooze(15000); + } + + // user let up on mouse button without dragging + Window()->Activate(); + Window()->UpdateIfNeeded(); + return kNotDragged; +} + + +void +BPoseView::DeleteSymLinkPoseTarget(const node_ref *itemNode, BPose *pose, + int32 index) +{ + ASSERT(pose->TargetModel()->IsSymLink()); + watch_node(itemNode, B_STOP_WATCHING, this); + BPoint loc(0, index * fListElemHeight); + pose->TargetModel()->SetLinkTo(0); + pose->UpdateBrokenSymLink(loc, this); +} + + +bool +BPoseView::DeletePose(const node_ref *itemNode, BPose *pose, int32 index) +{ + watch_node(itemNode, B_STOP_WATCHING, this); + + if (!pose) + pose = fPoseList->FindPose(itemNode, &index); + + if (pose) { + if (TargetModel()->IsSymLink()) { + Model *target = pose->TargetModel()->LinkTo(); + if (target) + watch_node(target->NodeRef(), B_STOP_WATCHING, this); + } + + ASSERT(TargetModel()); + + if (pose == fDropTarget) + fDropTarget = NULL; + + if (pose == ActivePose()) + CommitActivePose(); + + Window()->UpdateIfNeeded(); + + // remove it from list no matter what since it might be in list + // but not "selected" since selection is hidden + fSelectionList->RemoveItem(pose); + if (fSelectionPivotPose == pose) + fSelectionPivotPose = NULL; + if (fRealPivotPose == pose) + fRealPivotPose = NULL; + + if (pose->IsSelected() && fSelectionChangedHook) + ContainerWindow()->SelectionChanged(); + + fPoseList->RemoveItemAt(index); + fMimeTypeListIsDirty = true; + + if (pose->HasLocation()) + RemoveFromVSList(pose); + + BRect invalidRect; + if (ViewMode() == kListMode) + invalidRect = CalcPoseRect(pose, index); + else + invalidRect = pose->CalcRect(this); + + if (ViewMode() == kListMode) + CloseGapInList(&invalidRect); + else + RemoveFromExtent(invalidRect); + + Invalidate(invalidRect); + UpdateCount(); + UpdateScrollRange(); + ResetPosePlacementHint(); + + if (ViewMode() == kListMode) { + BRect bounds(Bounds()); + int32 index = (int32)(bounds.bottom / fListElemHeight); + BPose *pose = fPoseList->ItemAt(index); + if (!pose && bounds.top > 0) // scroll up a little + ScrollTo(bounds.left, max_c(bounds.top - fListElemHeight, 0)); + } + + delete pose; + + } else { + // we might be getting a delete for an item in the zombie list + Model *zombie = FindZombie(itemNode, &index); + if (zombie) { + PRINT(("deleting zombie model %s\n", zombie->Name())); + fZombieList->RemoveItemAt(index); + delete zombie; + } else + return false; + } + return true; +} + + +Model * +BPoseView::FindZombie(const node_ref *itemNode, int32 *resultingIndex) +{ + int32 count = fZombieList->CountItems(); + for (int32 index = 0; index < count; index++) { + Model *zombie = fZombieList->ItemAt(index); + if (*zombie->NodeRef() == *itemNode) { + if (resultingIndex) + *resultingIndex = index; + return zombie; + } + } + + return NULL; +} + +// return pose at location h,v (search list starting from bottom so +// drawing and hit detection reflect the same pose ordering) + +BPose * +BPoseView::FindPose(BPoint point, int32 *poseIndex) const +{ + if (ViewMode() == kListMode) { + int32 index = (int32)(point.y / fListElemHeight); + if (poseIndex) + *poseIndex = index; + + BPoint loc(0, index * fListElemHeight); + BPose *pose = fPoseList->ItemAt(index); + if (pose && pose->PointInPose(loc, this, point)) + return pose; + } else { + int32 count = fPoseList->CountItems(); + for (int32 index = count - 1; index >= 0; index--) { + BPose *pose = fPoseList->ItemAt(index); + if (pose->PointInPose(this, point)) { + if (poseIndex) + *poseIndex = index; + return pose; + } + } + } + + return NULL; +} + + +void +BPoseView::OpenSelection(BPose *clickedPose, int32 *index) +{ + BPose *singleWindowBrowsePose = clickedPose; + TrackerSettings settings; + + // Get first selected pose in selection if none was clicked + if (settings.SingleWindowBrowse() + && !singleWindowBrowsePose + && fSelectionList->CountItems() == 1 + && !IsFilePanel()) + singleWindowBrowsePose = fSelectionList->ItemAt(0); + + // check if we can use the single window mode + if (settings.SingleWindowBrowse() + && !IsDesktopWindow() + && !IsFilePanel() + && !(modifiers() & B_OPTION_KEY) + && TargetModel()->IsDirectory() + && singleWindowBrowsePose + && singleWindowBrowsePose->ResolvedModel() + && singleWindowBrowsePose->ResolvedModel()->IsDirectory()) { + // Switch to new directory + BMessage msg(kSwitchDirectory); + msg.AddRef("refs", singleWindowBrowsePose->ResolvedModel()->EntryRef()); + Window()->PostMessage(&msg); + } else + // Otherwise use standard method + OpenSelectionCommon(clickedPose, index, false); + +} + + +void +BPoseView::OpenSelectionUsing(BPose *clickedPose, int32 *index) +{ + OpenSelectionCommon(clickedPose, index, true); +} + + +void +BPoseView::OpenSelectionCommon(BPose *clickedPose, int32 *poseIndex, + bool openWith) +{ + int32 count = fSelectionList->CountItems(); + if (!count) + return; + + TTracker *tracker = dynamic_cast(be_app); + + BMessage message(B_REFS_RECEIVED); + + for (int32 index = 0; index < count; index++) { + BPose *pose = fSelectionList->ItemAt(index); + + message.AddRef("refs", pose->TargetModel()->EntryRef()); + + // close parent window if option down and we're not the desktop + // and we're not in single window mode + if (!tracker + || (modifiers() & B_OPTION_KEY) == 0 + || IsFilePanel() + || IsDesktopWindow() + || TrackerSettings().SingleWindowBrowse()) + continue; + + ASSERT(TargetModel()); + message.AddData("nodeRefsToClose", B_RAW_TYPE, TargetModel()->NodeRef(), + sizeof (node_ref)); + } + + if (openWith) + message.AddInt32("launchUsingSelector", 0); + + // add a messenger to the launch message that will be used to + // dispatch scripting calls from apps to the PoseView + message.AddMessenger("TrackerViewToken", BMessenger(this)); + + if (fSelectionHandler) + fSelectionHandler->PostMessage(&message); + + if (clickedPose) { + ASSERT(poseIndex); + if (ViewMode() == kListMode) + DrawOpenAnimation(CalcPoseRect(clickedPose, *poseIndex, true)); + else + DrawOpenAnimation(clickedPose->CalcRect(this)); + } +} + + +void +BPoseView::DrawOpenAnimation(BRect rect) +{ + SetDrawingMode(B_OP_INVERT); + + BRect box1(rect); + box1.InsetBy(rect.Width() / 2 - 2, rect.Height() / 2 - 2); + BRect box2(box1); + + for (int32 index = 0; index < 7; index++) { + box2 = box1; + box2.InsetBy(-2, -2); + StrokeRect(box1, B_MIXED_COLORS); + Sync(); + StrokeRect(box2, B_MIXED_COLORS); + Sync(); + snooze(10000); + StrokeRect(box1, B_MIXED_COLORS); + StrokeRect(box2, B_MIXED_COLORS); + Sync(); + box1 = box2; + } + + SetDrawingMode(B_OP_OVER); +} + + +void +BPoseView::UnmountSelectedVolumes() +{ + BVolume boot; + BVolumeRoster().GetBootVolume(&boot); + + int32 select_count = fSelectionList->CountItems(); + for (int32 index = 0; index < select_count; index++) { + Model *model = fSelectionList->ItemAt(index)->TargetModel(); + if (model->IsVolume()) { + BVolume volume(model->NodeRef()->device); + if (volume != boot) { + dynamic_cast(be_app)->SaveAllPoseLocations(); + + BMessage message(kUnmountVolume); + message.AddInt32("device_id", volume.Device()); + be_app->PostMessage(&message); + } + } + } +} + + +void +BPoseView::ClearPoses() +{ + CommitActivePose(); + SavePoseLocations(); + + // clear all pose lists + fPoseList->MakeEmpty(); + fMimeTypeListIsDirty = true; + fVSPoseList->MakeEmpty(); + fZombieList->MakeEmpty(); + fSelectionList->MakeEmpty(); + fSelectionPivotPose = NULL; + fRealPivotPose = NULL; + fMimeTypesInSelectionCache.MakeEmpty(); + + DisableScrollBars(); + ScrollTo(BPoint(0, 0)); + UpdateScrollRange(); + SetScrollBarsTo(BPoint(0, 0)); + EnableScrollBars(); + ResetPosePlacementHint(); + ClearExtent(); + + if (fSelectionChangedHook) + ContainerWindow()->SelectionChanged(); +} + + +void +BPoseView::SwitchDir(const entry_ref *newDirRef, AttributeStreamNode *node) +{ + ASSERT(TargetModel()); + if (*newDirRef == *TargetModel()->EntryRef()) + // no change + return; + + Model *model = new Model(newDirRef, true); + if (model->InitCheck() != B_OK || !model->IsDirectory()) { + delete model; + return; + } + + CommitActivePose(); + + // before clearing and adding new poses, we reset "blessed" async + // thread id to prevent old add_poses thread from adding any more icons + // the new add_poses thread will then set fAddPosesThread to its ID and it + // will be allowed to add icons + fAddPosesThreads.clear(); + + delete fModel; + fModel = model; + + // check if model is a trash dir, if so + // update ContainerWindow's fIsTrash, etc. + // variables to indicate new state + ContainerWindow()->UpdateIfTrash(model); + + StopWatching(); + ClearPoses(); + + // Restore state if requested + if (node) { + uint32 oldMode = ViewMode(); + + // Get new state + RestoreState(node); + + // Make sure the title view reset its items + fTitleView->Reset(); + + if (ViewMode() == kListMode && oldMode != kListMode) { + + MoveBy(0, kTitleViewHeight + 1); + ResizeBy(0, -(kTitleViewHeight + 1)); + + if (ContainerWindow()) + ContainerWindow()->ShowAttributeMenu(); + + fTitleView->ResizeTo(Frame().Width(), fTitleView->Frame().Height()); + fTitleView->MoveTo(Frame().left, Frame().top - (kTitleViewHeight + 1)); + if (Parent()) + Parent()->AddChild(fTitleView); + else + Window()->AddChild(fTitleView); + } else if (ViewMode() != kListMode && oldMode == kListMode) { + fTitleView->RemoveSelf(); + + if (ContainerWindow()) + ContainerWindow()->HideAttributeMenu(); + + MoveBy(0, -(kTitleViewHeight + 1)); + ResizeBy(0, kTitleViewHeight + 1); + } else if (ViewMode() == kListMode && oldMode == kListMode && fTitleView != NULL) + fTitleView->Invalidate(); + + BPoint origin; + if (ViewMode() == kListMode) + origin = fViewState->ListOrigin(); + else + origin = fViewState->IconOrigin(); + + PinPointToValidRange(origin); + + SetIconPoseHeight(); + GetLayoutInfo(ViewMode(), &fGrid, &fOffset); + ResetPosePlacementHint(); + + DisableScrollBars(); + ScrollTo(origin); + UpdateScrollRange(); + SetScrollBarsTo(origin); + EnableScrollBars(); + } + + StartWatching(); + + // be sure this happens after origin is set and window is sized + // properly for proper icon caching! + + if (ContainerWindow()->IsTrash()) + AddTrashPoses(); + else AddPoses(TargetModel()); + TargetModel()->CloseNode(); + + Invalidate(); + ResetOrigin(); + ResetPosePlacementHint(); + + fLastKeyTime = 0; +} + + +void +BPoseView::Refresh() +{ + BEntry entry; + + ASSERT(TargetModel()); + if (TargetModel()->OpenNode() != B_OK) + return; + + StopWatching(); + ClearPoses(); + StartWatching(); + + // be sure this happens after origin is set and window is sized + // properly for proper icon caching! + AddPoses(TargetModel()); + TargetModel()->CloseNode(); + + Invalidate(); + ResetOrigin(); + ResetPosePlacementHint(); +} + + +void +BPoseView::ResetOrigin() +{ + DisableScrollBars(); + ScrollTo(B_ORIGIN); + UpdateScrollRange(); + SetScrollBarsTo(B_ORIGIN); + EnableScrollBars(); +} + + +void +BPoseView::EditQueries() +{ + // edit selected queries + SendSelectionAsRefs(kEditQuery, true); +} + + +void +BPoseView::SendSelectionAsRefs(uint32 what, bool onlyQueries) +{ + // fix this by having a proper selection iterator + + int32 numItems = fSelectionList->CountItems(); + if (!numItems) + return; + + bool haveRef = false; + BMessage message; + message.what = what; + + for (int32 index = 0; index < numItems; index++) { + BPose *pose = fSelectionList->ItemAt(index); + if (onlyQueries) { + // to check if pose is a query, follow any symlink first + BEntry resolvedEntry(pose->TargetModel()->EntryRef(), true); + if (resolvedEntry.InitCheck() != B_OK) + continue; + + Model model(&resolvedEntry); + if (!model.IsQuery() && !model.IsQueryTemplate()) + continue; + } + haveRef = true; + message.AddRef("refs", pose->TargetModel()->EntryRef()); + } + if (!haveRef) + return; + + if (onlyQueries) + // this is used to make query templates come up in a special edit window + message.AddBool("editQueryOnPose", &onlyQueries); + + BMessenger(kTrackerSignature).SendMessage(&message); +} + + +void +BPoseView::OpenInfoWindows() +{ + BMessenger tracker(kTrackerSignature); + if (!tracker.IsValid()) { + (new BAlert("", "The Tracker must be running to see Info windows.", + "Cancel", NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); + return; + } + SendSelectionAsRefs(kGetInfo); +} + + +void +BPoseView::SetDefaultPrinter() +{ + BMessenger tracker(kTrackerSignature); + if (!tracker.IsValid()) { + (new BAlert("", "The Tracker must be running to see set the default printer.", + "Cancel", NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); + return; + } + SendSelectionAsRefs(kMakeActivePrinter); +} + + +void +BPoseView::OpenParent() +{ + if (!TargetModel() || TargetModel()->IsRoot() || IsDesktopWindow()) + return; + + BEntry entry(TargetModel()->EntryRef()); + BDirectory parent; + entry_ref ref; + + if (entry.GetParent(&parent) != B_OK + || parent.GetEntry(&entry) != B_OK + || entry.GetRef(&ref) != B_OK) + return; + + BEntry root("/"); + if (!TrackerSettings().ShowDisksIcon() && entry == root + && (modifiers() & B_CONTROL_KEY) == 0) + return; + + Model parentModel(&ref); + + BMessage message(B_REFS_RECEIVED); + message.AddRef("refs", &ref); + + if (dynamic_cast(be_app)) { + // add information about the child, so that we can select it + // in the parent view + message.AddData("nodeRefToSelect", B_RAW_TYPE, TargetModel()->NodeRef(), + sizeof (node_ref)); + + if ((modifiers() & B_OPTION_KEY) != 0 && !IsFilePanel()) + // if option down, add instructions to close the parent + message.AddData("nodeRefsToClose", B_RAW_TYPE, TargetModel()->NodeRef(), + sizeof (node_ref)); + } + + be_app->PostMessage(&message); +} + + +void +BPoseView::IdentifySelection() +{ + bool force = (modifiers() & B_OPTION_KEY) != 0; + int32 count = fSelectionList->CountItems(); + for (int32 index = 0; index < count; index++) { + BPose *pose = fSelectionList->ItemAt(index); + BEntry entry(pose->TargetModel()->EntryRef()); + if (entry.InitCheck() == B_OK) { + BPath path; + if (entry.GetPath(&path) == B_OK) + update_mime_info(path.Path(), true, false, force ? 2 : 1); + } + } +} + + +void +BPoseView::ClearSelection() +{ + CommitActivePose(); + fSelectionPivotPose = NULL; + fRealPivotPose = NULL; + + if (fSelectionList->CountItems()) { + + // scan all visible poses first + BRect bounds(Bounds()); + + if (ViewMode() == kListMode) { + int32 startIndex = (int32)(bounds.top / fListElemHeight); + BPoint loc(0, startIndex * fListElemHeight); + int32 count = fPoseList->CountItems(); + for (int32 index = startIndex; index < count; index++) { + BPose *pose = fPoseList->ItemAt(index); + if (pose->IsSelected()) { + pose->Select(false); + pose->Draw(pose->CalcRect(loc, this, false), this, false); + } + + loc.y += fListElemHeight; + if (loc.y > bounds.bottom) + break; + } + } else { + int32 startIndex = FirstIndexAtOrBelow((int32)(bounds.top - IconPoseHeight()), true); + int32 count = fVSPoseList->CountItems(); + for (int32 index = startIndex; index < count; index++) { + BPose *pose = fVSPoseList->ItemAt(index); + if (pose) { + if (pose->IsSelected()) { + pose->Select(false); + BRect poseRect(pose->CalcRect(this)); + if (EraseWidgetTextBackground()) + pose->Draw(poseRect, this, false); + else + Invalidate(poseRect); + } + + if (pose->Location().y > bounds.bottom) + break; + } + } + } + + // clear selection state in all poses + int32 count = fSelectionList->CountItems(); + for (int32 index = 0; index < count; index++) + fSelectionList->ItemAt(index)->Select(false); + + fSelectionList->MakeEmpty(); + } + fMimeTypesInSelectionCache.MakeEmpty(); +} + + +void +BPoseView::ShowSelection(bool show) +{ + if (fSelectionVisible == show) + return; + + fSelectionVisible = show; + + if (fSelectionList->CountItems()) { + + // scan all visible poses first + BRect bounds(Bounds()); + + if (ViewMode() == kListMode) { + int32 startIndex = (int32)(bounds.top / fListElemHeight); + BPoint loc(0, startIndex * fListElemHeight); + int32 count = fPoseList->CountItems(); + for (int32 index = startIndex; index < count; index++) { + BPose *pose = fPoseList->ItemAt(index); + if (fSelectionList->HasItem(pose)) + if (pose->IsSelected() != show || fShowSelectionWhenInactive) { + if (!fShowSelectionWhenInactive) + pose->Select(show); + pose->Draw(BRect(pose->CalcRect(loc, this, false)), this, false); + } + + loc.y += fListElemHeight; + if (loc.y > bounds.bottom) + break; + } + } else { + int32 startIndex = FirstIndexAtOrBelow((int32)(bounds.top - IconPoseHeight()), true); + int32 count = fVSPoseList->CountItems(); + for (int32 index = startIndex; index < count; index++) { + BPose *pose = fVSPoseList->ItemAt(index); + if (pose) { + if (fSelectionList->HasItem(pose)) + if (pose->IsSelected() != show || fShowSelectionWhenInactive) { + if (!fShowSelectionWhenInactive) + pose->Select(show); + if (show && EraseWidgetTextBackground()) + pose->Draw(pose->CalcRect(this), this, false); + else + Invalidate(pose->CalcRect(this)); + } + + if (pose->Location().y > bounds.bottom) + break; + } + } + } + + // now set all other poses + int32 count = fSelectionList->CountItems(); + for (int32 index = 0; index < count; index++) { + BPose *pose = fSelectionList->ItemAt(index); + if (pose->IsSelected() != show && !fShowSelectionWhenInactive) + pose->Select(show); + } + + // finally update fRealPivotPose/fSelectionPivotPose + if (!show) { + fRealPivotPose = fSelectionPivotPose; + fSelectionPivotPose = NULL; + } else { + if (fRealPivotPose) + fSelectionPivotPose = fRealPivotPose; + fRealPivotPose = NULL; + } + } +} + + +void +BPoseView::AddRemovePoseFromSelection(BPose *pose, int32 index, bool select) +{ + // Do not allow double selection/deselection. + if (select == pose->IsSelected()) + return; + + pose->Select(select); + + // update display + if (EraseWidgetTextBackground()) + DrawPose(pose, index, false); + else + Invalidate(pose->CalcRect(this)); + + if (select) + fSelectionList->AddItem(pose); + else { + fSelectionList->RemoveItem(pose); + if (fSelectionPivotPose == pose) + fSelectionPivotPose = NULL; + if (fRealPivotPose == pose) + fRealPivotPose = NULL; + } +} + + +void +BPoseView::RemoveFromExtent(const BRect &rect) +{ + ASSERT(ViewMode() != kListMode); + + if (rect.left <= fExtent.left || rect.right >= fExtent.right + || rect.top <= fExtent.top || rect.bottom >= fExtent.bottom) + RecalcExtent(); +} + + +void +BPoseView::RecalcExtent() +{ + ASSERT(ViewMode() != kListMode); + + ClearExtent(); + int32 count = fPoseList->CountItems(); + for (int32 index = 0; index < count; index++) + AddToExtent(fPoseList->ItemAt(index)->CalcRect(this)); +} + + +BRect +BPoseView::Extent() const +{ + BRect rect; + + if (ViewMode() == kListMode) { + BColumn *column = fColumnList->LastItem(); + if (column) { + rect.left = rect.top = 0; + rect.right = column->Offset() + column->Width(); + rect.bottom = fListElemHeight * fPoseList->CountItems(); + } else + rect.Set(LeftTop().x, LeftTop().y, LeftTop().x, LeftTop().y); + + } else { + rect = fExtent; + rect.left -= fOffset.x; + rect.top -= fOffset.y; + rect.right += fOffset.x; + rect.bottom += fOffset.y; + if (!rect.IsValid()) + rect.Set(LeftTop().x, LeftTop().y, LeftTop().x, LeftTop().y); + } + + return rect; +} + + +void +BPoseView::SetScrollBarsTo(BPoint point) +{ + BPoint origin; + + if (fHScrollBar && fVScrollBar) { + fHScrollBar->SetValue(point.x); + fVScrollBar->SetValue(point.y); + } else { + origin = LeftTop(); + ScrollTo(BPoint(point.x, origin.y)); + ScrollTo(BPoint(origin.x, point.y)); + } +} + + +void +BPoseView::PinPointToValidRange(BPoint& origin) +{ + // !NaN and valid range + // the following checks are not broken even they look like they are + if (!(origin.x >= 0) && !(origin.x <= 0)) + origin.x = 0; + else if (origin.x < -40000.0 || origin.x > 40000.0) + origin.x = 0; + + if (!(origin.y >= 0) && !(origin.y <= 0)) + origin.y = 0; + else if (origin.y < -40000.0 || origin.y > 40000.0) + origin.y = 0; +} + + +void +BPoseView::UpdateScrollRange() +{ + // ToDo: + // some calls to UpdateScrollRange don't do the right thing because + // Extent doesn't return the right value (too early in PoseView lifetime??) + // + // This happened most with file panels, when opening a parent - added + // an extra call to UpdateScrollRange in SelectChildInParent to work + // around this + + AutoLock lock(Window()); + if (!lock) + return; + + BRect bounds(Bounds()); + + BPoint origin(LeftTop()); + BRect extent(Extent()); + + lock.Unlock(); + + BPoint minVal(std::min(extent.left, origin.x), std::min(extent.top, origin.y)); + + BPoint maxVal((extent.right - bounds.right) + origin.x, + (extent.bottom - bounds.bottom) + origin.y); + + maxVal.x = std::max(maxVal.x, origin.x); + maxVal.y = std::max(maxVal.y, origin.y); + + if (fHScrollBar) { + float scrollMin; + float scrollMax; + fHScrollBar->GetRange(&scrollMin, &scrollMax); + if (minVal.x != scrollMin || maxVal.x != scrollMax) { + fHScrollBar->SetRange(minVal.x, maxVal.x); + fHScrollBar->SetSteps(kSmallStep, bounds.Width()); + } + } + + if (fVScrollBar) { + float scrollMin; + float scrollMax; + fVScrollBar->GetRange(&scrollMin, &scrollMax); + + if (minVal.y != scrollMin || maxVal.y != scrollMax) { + fVScrollBar->SetRange(minVal.y, maxVal.y); + fVScrollBar->SetSteps(kSmallStep, bounds.Height()); + } + } + + // set proportions for bars + BRect visibleExtent(extent & bounds); + BRect totalExtent(extent | bounds); + + if (fHScrollBar) { + float proportion = visibleExtent.Width() / totalExtent.Width(); + if (fHScrollBar->Proportion() != proportion) + fHScrollBar->SetProportion(proportion); + } + + if (fVScrollBar) { + float proportion = visibleExtent.Height() / totalExtent.Height(); + if (fVScrollBar->Proportion() != proportion) + fVScrollBar->SetProportion(proportion); + } +} + + +void +BPoseView::DrawPose(BPose *pose, int32 index, bool fullDraw) +{ + BRect rect; + if (ViewMode() == kListMode) + rect = pose->CalcRect(BPoint(0, index * fListElemHeight), this, fullDraw); + else + rect = pose->CalcRect(this); + + if (TrackerSettings().ShowVolumeSpaceBar() && pose->TargetModel()->IsVolume()) + Invalidate(rect); + else + pose->Draw(rect, this, fullDraw); +} + + +rgb_color +BPoseView::DeskTextColor() const +{ + rgb_color color = ViewColor(); + float thresh = color.red + (color.green * 1.5f) + (color.blue * .50f); + + if (thresh >= 300) { + color.red = 0; + color.green = 0; + color.blue = 0; + } else { + color.red = 255; + color.green = 255; + color.blue = 255; + } + + return color; +} + + +rgb_color +BPoseView::DeskTextBackColor() const +{ + // returns black or white color depending on the desktop background + int32 thresh = 0; + rgb_color color = LowColor(); + + if (color.red > 150) + thresh++; + if (color.green > 150) + thresh++; + if (color.blue > 150) + thresh++; + + if (thresh > 1) { + color.red = 255; + color.green = 255; + color.blue = 255; + } else { + color.red = 0; + color.green = 0; + color.blue = 0; + } + + return color; +} + + +void +BPoseView::Draw(BRect updateRect) +{ + if (IsDesktopWindow()) { + BScreen screen(Window()); + rgb_color color = screen.DesktopColor(); + SetLowColor(color); + SetViewColor(color); + } + DrawViewCommon(updateRect); + + if (fTransparentSelection && fSelectionRect.IsValid()) { + SetDrawingMode(B_OP_ALPHA); + SetHighColor(255, 255, 255, 128); + if (fSelectionRect.Width() == 0 || fSelectionRect.Height() == 0) + StrokeLine(fSelectionRect.LeftTop(), fSelectionRect.RightBottom()); + else { + StrokeRect(fSelectionRect); + BRect interior = fSelectionRect; + interior.InsetBy(1, 1); + if (interior.IsValid()) { + SetHighColor(80, 80, 80, 90); + FillRect(interior); + } + } + SetDrawingMode(B_OP_OVER); + } +} + + +void +BPoseView::SynchronousUpdate(BRect updateRect, bool clip) +{ + if (clip) { + BRegion updateRegion; + updateRegion.Set(updateRect); + ConstrainClippingRegion(&updateRegion); + } + + FillRect(updateRect, B_SOLID_LOW); + DrawViewCommon(updateRect); + + if (clip) + ConstrainClippingRegion(0); +} + + +void +BPoseView::DrawViewCommon(BRect updateRect, bool recalculateText) +{ + GetClippingRegion(fUpdateRegion); + + int32 count = fPoseList->CountItems(); + if (ViewMode() == kListMode) { + int32 startIndex = (int32)((updateRect.top - fListElemHeight) / fListElemHeight); + if (startIndex < 0) + startIndex = 0; + + BPoint loc(0, startIndex * fListElemHeight); + + for (int32 index = startIndex; index < count; index++) { + BPose *pose = fPoseList->ItemAt(index); + BRect poseRect(pose->CalcRect(loc, this, true)); + pose->Draw(poseRect, this, true, fUpdateRegion, recalculateText); + loc.y += fListElemHeight; + if (loc.y >= updateRect.bottom) + break; + } + } else { + for (int32 index = 0; index < count; index++) { + BPose *pose = fPoseList->ItemAt(index); + BRect poseRect(pose->CalcRect(this)); + if (fUpdateRegion->Intersects(poseRect)) + pose->Draw(poseRect, this, true, fUpdateRegion); + } + } +} + + +void +BPoseView::ColumnRedraw(BRect updateRect) +{ + // used for dynamic column resizing using an offscreen draw buffer + ASSERT(ViewMode() == kListMode); + + if (IsDesktopWindow()) { + BScreen screen(Window()); + rgb_color d = screen.DesktopColor(); + SetLowColor(d); + SetViewColor(d); + } + + int32 startIndex = (int32)((updateRect.top - fListElemHeight) / fListElemHeight); + if (startIndex < 0) + startIndex = 0; + + int32 count = fPoseList->CountItems(); + if (!count) + return; + + BPoint loc(0, startIndex * fListElemHeight); + BRect srcRect = fPoseList->ItemAt(0)->CalcRect(BPoint(0, 0), this, false); + srcRect.right += 1024; // need this to erase correctly + fOffscreen->BeginUsing(srcRect); + BView *offscreenView = fOffscreen->View(); + + BRegion updateRegion; + updateRegion.Set(updateRect); + ConstrainClippingRegion(&updateRegion); + + for (int32 index = startIndex; index < count; index++) { + BPose *pose = fPoseList->ItemAt(index); + + offscreenView->SetDrawingMode(B_OP_COPY); + offscreenView->SetLowColor(LowColor()); + offscreenView->FillRect(offscreenView->Bounds(), B_SOLID_LOW); + + BRect dstRect = srcRect; + dstRect.OffsetTo(loc); + + BPoint offsetBy(0, -(index * ListElemHeight())); + pose->Draw(dstRect, this, offscreenView, true, &updateRegion, + offsetBy, pose->IsSelected()); + + offscreenView->Sync(); + SetDrawingMode(B_OP_COPY); + DrawBitmap(fOffscreen->Bitmap(), srcRect, dstRect); + loc.y += fListElemHeight; + if (loc.y > updateRect.bottom) + break; + } + fOffscreen->DoneUsing(); + ConstrainClippingRegion(0); +} + + +void +BPoseView::CloseGapInList(BRect *invalidRect) +{ + (*invalidRect).bottom = Extent().bottom + fListElemHeight; + BRect bounds(Bounds()); + + if (bounds.Intersects(*invalidRect)) { + BRect destRect(*invalidRect); + destRect = destRect & bounds; + destRect.bottom -= fListElemHeight; + + BRect srcRect(destRect); + srcRect.OffsetBy(0, fListElemHeight); + + if (srcRect.Intersects(bounds) || destRect.Intersects(bounds)) + CopyBits(srcRect, destRect); + + *invalidRect = srcRect; + (*invalidRect).top = destRect.bottom; + } +} + + +void +BPoseView::CheckPoseSortOrder(BPose *pose, int32 oldIndex) +{ + if (ViewMode() != kListMode) + return; + + Window()->UpdateIfNeeded(); + + // take pose out of list for BSearch + fPoseList->RemoveItemAt(oldIndex); + int32 afterIndex; + int32 orientation = BSearchList(pose, &afterIndex); + + int32 newIndex; + if (orientation == kInsertAtFront) + newIndex = 0; + else + newIndex = afterIndex + 1; + + if (newIndex == oldIndex) { + fPoseList->AddItem(pose, oldIndex); + return; + } + + BRect invalidRect(CalcPoseRect(pose, oldIndex)); + CloseGapInList(&invalidRect); + Invalidate(invalidRect); + // need to invalidate for the last item in the list + InsertPoseAfter(pose, &afterIndex, orientation, &invalidRect); + fPoseList->AddItem(pose, newIndex); + Invalidate(invalidRect); +} + + +static int +PoseCompareAddWidget(const BPose *p1, const BPose *p2, BPoseView *view) +{ + // pose comparison and lazy text widget adding + + uint32 sort = view->PrimarySort(); + BColumn *column = view->ColumnFor(sort); + if (!column) + return 0; + + BPose *primary; + BPose *secondary; + if (!view->ReverseSort()) { + primary = const_cast(p1); + secondary = const_cast(p2); + } else { + primary = const_cast(p2); + secondary = const_cast(p1); + } + + int32 result = 0; + for (int32 count = 0; ; count++) { + + BTextWidget *widget1 = primary->WidgetFor(sort); + if (!widget1) + widget1 = primary->AddWidget(view, column); + + BTextWidget *widget2 = secondary->WidgetFor(sort); + if (!widget2) + widget2 = secondary->AddWidget(view, column); + + if (!widget1 || !widget2) + return result; + + result = widget1->Compare(*widget2, view); + + if (count) + return result; + + // do we need to sort by secondary attribute? + if (result == 0) { + sort = view->SecondarySort(); + if (!sort) + return result; + + column = view->ColumnFor(sort); + if (!column) + return result; + } + } + + return result; +} + + +static BPose * +BSearch(PoseList *table, const BPose* key, BPoseView *view, + int (*cmp)(const BPose *, const BPose *, BPoseView *)) +{ + int32 r = table->CountItems(); + BPose *result = 0; + + for (int32 l = 1; l <= r;) { + int32 m = (l + r) / 2; + + result = table->ItemAt(m - 1); + int32 compareResult = (cmp)(result, key, view); + if (compareResult == 0) + return result; + else if (compareResult < 0) + l = m + 1; + else + r = m - 1; + } + + return result; +} + + +int32 +BPoseView::BSearchList(const BPose *pose, int32 *resultingIndex) +{ + // check to see if insertion should be at beginning of list + const BPose *firstPose = fPoseList->FirstItem(); + if (!firstPose) + return kInsertAtFront; + + if (PoseCompareAddWidget(pose, firstPose, this) <= 0) { + *resultingIndex = 0; + return kInsertAtFront; + } + + int32 count = fPoseList->CountItems(); + *resultingIndex = count - 1; + + const BPose *searchResult = BSearch(fPoseList, pose, this, PoseCompareAddWidget); + + if (searchResult) { + // what are we doing here?? + // looks like we are skipping poses with identical search results or + // something + int32 index = fPoseList->IndexOf(searchResult); + for (; index < count; index++) { + int32 result = PoseCompareAddWidget(pose, fPoseList->ItemAt(index), this); + if (result <= 0) { + --index; + break; + } + } + + if (index != count) + *resultingIndex = index; + } + + return kInsertAfter; +} + + +void +BPoseView::SetPrimarySort(uint32 attrHash) +{ + BColumn *column = ColumnFor(attrHash); + + if (column) { + fViewState->SetPrimarySort(attrHash); + fViewState->SetPrimarySortType(column->AttrType()); + } +} + + +void +BPoseView::SetSecondarySort(uint32 attrHash) +{ + BColumn *column = ColumnFor(attrHash); + + if (column) { + fViewState->SetSecondarySort(attrHash); + fViewState->SetSecondarySortType(column->AttrType()); + } else { + fViewState->SetSecondarySort(0); + fViewState->SetSecondarySortType(0); + } +} + + +void +BPoseView::SetReverseSort(bool reverse) +{ + fViewState->SetReverseSort(reverse); +} + + +inline int +PoseCompareAddWidgetBinder(const BPose *p1, const BPose *p2, void *castToPoseView) +{ + return PoseCompareAddWidget(p1, p2, (BPoseView *)castToPoseView); +} + + +#if xDEBUG +static BPose * +DumpOne(BPose *pose, void *) +{ + pose->TargetModel()->PrintToStream(0); + return 0; +} +#endif + + +void +BPoseView::SortPoses() +{ + CommitActivePose(); + // PRINT(("pose list count %d\n", fPoseList->CountItems())); +#if xDEBUG + fPoseList->EachElement(DumpOne, 0); + PRINT(("===================\n")); +#endif + + fPoseList->SortItems(PoseCompareAddWidgetBinder, this); +} + + +BColumn * +BPoseView::ColumnFor(uint32 attr) const +{ + int32 count = fColumnList->CountItems(); + for (int32 index = 0; index < count; index++) { + BColumn *column = ColumnAt(index); + if (column->AttrHash() == attr) + return column; + } + + return NULL; +} + + +bool // returns true if actually resized +BPoseView::ResizeColumnToWidest(BColumn *column) +{ + ASSERT(ViewMode() == kListMode); + + float maxWidth = 0; + + int32 count = fPoseList->CountItems(); + for (int32 i = 0; i < count; ++i) { + BTextWidget *widget = fPoseList->ItemAt(i)->WidgetFor(column->AttrHash()); + if (widget) { + float width = widget->PreferredWidth(this); + if (width > maxWidth) + maxWidth = width; + } + } + + if (maxWidth > 0) { + ResizeColumn(column, maxWidth); + return true; + } + + return false; +} + + +const int32 kRoomForLine = 2; + +BPoint +BPoseView::ResizeColumn(BColumn *column, float newSize, + float *lastLineDrawPos, + void (*drawLineFunc)(BPoseView *, BPoint, BPoint), + void (*undrawLineFunc)(BPoseView *, BPoint, BPoint)) +{ + BRect sourceRect(Bounds()); + BPoint result(sourceRect.RightBottom()); + + BRect destRect(sourceRect); + // we will use sourceRect and destRect for copyBits + BRect invalidateRect(sourceRect); + // this will serve to clean up after the invalidate + BRect columnDrawRect(sourceRect); + // we will use columnDrawRect to draw the actual resized column + + + bool shrinking = newSize < column->Width(); + columnDrawRect.left = column->Offset(); + columnDrawRect.right = column->Offset() + kTitleColumnRightExtraMargin + - kRoomForLine + newSize; + sourceRect.left = column->Offset() + kTitleColumnRightExtraMargin + - kRoomForLine + column->Width(); + destRect.left = columnDrawRect.right; + destRect.right = destRect.left + sourceRect.Width(); + invalidateRect.left = destRect.right; + invalidateRect.right = sourceRect.right; + + column->SetWidth(newSize); + + float offset = kColumnStart; + BColumn *last = fColumnList->FirstItem(); + + + int32 count = fColumnList->CountItems(); + for (int32 index = 0; index < count; index++) { + column = fColumnList->ItemAt(index); + column->SetOffset(offset); + last = column; + offset = last->Offset() + last->Width() + kTitleColumnExtraMargin; + } + + if (shrinking) { + ColumnRedraw(columnDrawRect); + // dont have to undraw when shrinking + CopyBits(sourceRect, destRect); + if (drawLineFunc) { + ASSERT(lastLineDrawPos); + (drawLineFunc)(this, BPoint(destRect.left + kRoomForLine, destRect.top), + BPoint(destRect.left + kRoomForLine, destRect.bottom)); + *lastLineDrawPos = destRect.left + kRoomForLine; + } + } else { + CopyBits(sourceRect, destRect); + if (undrawLineFunc) { + ASSERT(lastLineDrawPos); + (undrawLineFunc)(this, BPoint(*lastLineDrawPos, sourceRect.top), + BPoint(*lastLineDrawPos, sourceRect.bottom)); + } + if (drawLineFunc) { + ASSERT(lastLineDrawPos); +#if 0 + (drawLineFunc)(this, BPoint(destRect.left + kRoomForLine, destRect.top), + BPoint(destRect.left + kRoomForLine, destRect.bottom)); +#endif + *lastLineDrawPos = destRect.left + kRoomForLine; + } + ColumnRedraw(columnDrawRect); + } + if (invalidateRect.left < invalidateRect.right) + SynchronousUpdate(invalidateRect, true); + + fStateNeedsSaving = true; + + return result; +} + + +void +BPoseView::MoveColumnTo(BColumn *src, BColumn *dest) +{ + // find the leftmost boundary of columns we are about to reshuffle + float miny = src->Offset(); + if (miny > dest->Offset()) + miny = dest->Offset(); + + // ensure columns are in proper order in list + int32 index = fColumnList->IndexOf(dest); + fColumnList->RemoveItem(src, false); + fColumnList->AddItem(src, index); + + float offset = kColumnStart; + BColumn *last = fColumnList->FirstItem(); + int32 count = fColumnList->CountItems(); + + for (int32 index = 0; index < count; index++) { + BColumn *column = fColumnList->ItemAt(index); + column->SetOffset(offset); + last = column; + offset = last->Offset() + last->Width() + kTitleColumnExtraMargin; + } + + // invalidate everything to the right of miny + BRect bounds(Bounds()); + bounds.left = miny; + Invalidate(bounds); + + fStateNeedsSaving = true; +} + + +void +BPoseView::MouseMoved(BPoint mouseLoc, uint32 moveCode, const BMessage *message) +{ + if (!fDropEnabled || !message) + return; + + BContainerWindow* window = dynamic_cast(Window()); + if (!window) + return; + + switch (moveCode) { + case B_INSIDE_VIEW: + case B_ENTERED_VIEW: + UpdateDropTarget(mouseLoc, message, window->ContextMenu()); + if (fDropTarget) { + bigtime_t dropMenuDelay; + get_click_speed(&dropMenuDelay); + dropMenuDelay *= 3; + + BContainerWindow *window = ContainerWindow(); + if (!window || !message || window->ContextMenu()) + break; + + bigtime_t clickTime = system_time(); + BPoint loc; + uint32 buttons; + GetMouse(&loc, &buttons); + for (;;) { + if (buttons == 0 + || fabs(loc.x - mouseLoc.x) > 4 || fabs(loc.y - mouseLoc.y) > 4) + // only loop if mouse buttons are down + // moved the mouse, cancel showing the context menu + break; + + // handle drag and drop + bigtime_t now = system_time(); + // use shift key to get around over-loading of Control key + // for context menus and auto-dnd menu + if (((modifiers() & B_SHIFT_KEY) + && (now - clickTime) > 200000) + || now - clickTime > dropMenuDelay) { + // let go of button or pressing for a while, show menu now + window->DragStart(message); + FrameForPose(fDropTarget, true, &fStartFrame); + ShowContextMenu(mouseLoc); + break; + } + + snooze(10000); + GetMouse(&loc, &buttons); + } + + } + break; + + case B_EXITED_VIEW: + // ToDo: + // autoscroll here + if (!window->ContextMenu()) { + HiliteDropTarget(false); + fDropTarget = NULL; + } + break; + } +} + + +bool +BPoseView::UpdateDropTarget(BPoint mouseLoc, const BMessage *dragMessage, + bool trackingContextMenu) +{ + ASSERT(dragMessage); + + int32 index; + BPose *targetPose = FindPose(mouseLoc, &index); + + if (targetPose == fDropTarget + || (trackingContextMenu && !targetPose)) + // no change + return false; + + if (fDropTarget) + HiliteDropTarget(false); + + fDropTarget = targetPose; + + // dereference if symlink + Model *targetModel = NULL; + if (targetPose) + targetModel = targetPose->TargetModel(); + Model tmpTarget; + if (targetModel && targetModel->IsSymLink() + && tmpTarget.SetTo(targetPose->TargetModel()->EntryRef(), true, true) == B_OK) + targetModel = &tmpTarget; + + bool ignoreTypes = (modifiers() & B_CONTROL_KEY) != 0; + if (targetPose && CanHandleDragSelection(targetModel, dragMessage, ignoreTypes)) { + // new target is valid, select it + HiliteDropTarget(true); + } else + fDropTarget = NULL; + + return true; +} + + +bool +BPoseView::FrameForPose(BPose *targetpose, bool convert, BRect *poseRect) +{ + bool returnvalue = false; + BRect bounds(Bounds()); + + if (ViewMode() == kListMode) { + int32 count = fPoseList->CountItems(); + int32 startIndex = (int32)(bounds.top / fListElemHeight); + + BPoint loc(0, startIndex * fListElemHeight); + + for (int32 index = startIndex; index < count; index++) { + if (targetpose == fPoseList->ItemAt(index)) { + *poseRect = fDropTarget->CalcRect(loc, this, false); + returnvalue = true; + } + + loc.y += fListElemHeight; + if (loc.y > bounds.bottom) + returnvalue = false; + } + } else { + int32 startIndex = FirstIndexAtOrBelow((int32)(bounds.top - IconPoseHeight()), true); + int32 count = fVSPoseList->CountItems(); + + for (int32 index = startIndex; index < count; index++) { + BPose *pose = fVSPoseList->ItemAt(index); + if (pose) { + if (pose == fDropTarget) { + *poseRect = pose->CalcRect(this); + returnvalue = true; + break; + } + + if (pose->Location().y > bounds.bottom) { + returnvalue = false; + break; + } + } + } + } + + if (convert) + ConvertToScreen(poseRect); + + return returnvalue; +} + + +const int32 kMenuTrackMargin = 20; +bool +BPoseView::MenuTrackingHook(BMenu *menu, void *) +{ + // return true if the menu should go away + if (!menu->LockLooper()) + return false; + + uint32 buttons; + BPoint location; + menu->GetMouse(&location, &buttons); + + bool returnvalue = true; + // don't test for buttons up here and try to circumvent messaging + // lest you miss an invoke that will happen after the window goes away + + BRect bounds(menu->Bounds()); + bounds.InsetBy(-kMenuTrackMargin, -kMenuTrackMargin); + if (bounds.Contains(location)) + // still in menu + returnvalue = false; + + + if (returnvalue) { + menu->ConvertToScreen(&location); + int32 count = menu->CountItems(); + for (int32 index = 0 ; index < count; index++) { + // iterate through all of the items in the menu + // if the submenu is showing, + // see if the mouse is in the submenu + BMenuItem *item = menu->ItemAt(index); + if (item && item->Submenu()) { + BWindow *window = item->Submenu()->Window(); + bool inSubmenu = false; + if (window && window->Lock()) { + if (!window->IsHidden()) { + BRect frame(window->Frame()); + + frame.InsetBy(-kMenuTrackMargin, -kMenuTrackMargin); + inSubmenu = frame.Contains(location); + } + window->Unlock(); + if (inSubmenu) { + // only one menu can have its window open + // bail now + returnvalue = false; + break; + } + } + } + } + } + + menu->UnlockLooper(); + + return returnvalue; +} + + +void +BPoseView::DragStop() +{ + fStartFrame.Set(0, 0, 0, 0); + BContainerWindow *window = ContainerWindow(); + if (window) + window->DragStop(); +} + + +void +BPoseView::HiliteDropTarget(bool hiliteState) +{ + // hilites current drop target while dragging, does not modify selection list + if (!fDropTarget) + return; + + // drop target already has the desired state + if (fDropTarget->IsSelected() == hiliteState || (!hiliteState && fDropTargetWasSelected)) { + fDropTargetWasSelected = hiliteState; + return; + } + + fDropTarget->Select(hiliteState); + + // scan all visible poses + BRect bounds(Bounds()); + + if (ViewMode() == kListMode) { + int32 count = fPoseList->CountItems(); + int32 startIndex = (int32)(bounds.top / fListElemHeight); + + BPoint loc(0, startIndex * fListElemHeight); + + for (int32 index = startIndex; index < count; index++) { + if (fDropTarget == fPoseList->ItemAt(index)) { + BRect poseRect = fDropTarget->CalcRect(loc, this, false); + fDropTarget->Draw(poseRect, this, false); + break; + } + + loc.y += fListElemHeight; + if (loc.y > bounds.bottom) + break; + } + } else { + int32 startIndex = FirstIndexAtOrBelow((int32)(bounds.top - IconPoseHeight()), true); + int32 count = fVSPoseList->CountItems(); + + for (int32 index = startIndex; index < count; index++) { + BPose *pose = fVSPoseList->ItemAt(index); + if (pose) { + if (pose == fDropTarget) { + if (!hiliteState && !EraseWidgetTextBackground()) + // deselecting an icon with widget drawn over background + // have to be a little tricky here - draw just the icon, + // invalidate the widget + pose->DeselectWithoutErasingBackground(pose->CalcRect(this), this); + else + pose->Draw(pose->CalcRect(this), this, false); + break; + } + + if (pose->Location().y > bounds.bottom) + break; + } + } + } +} + + +bool +BPoseView::CheckAutoScroll(BPoint mouseLoc, bool shouldScroll, + bool selectionScrolling) +{ + if (!fShouldAutoScroll) + return false; + + // make sure window is in front before attempting scrolling + BContainerWindow *window = ContainerWindow(); + if (!window) + return false; + + // selection scrolling will also work if the window is inactive + if (!selectionScrolling && !window->IsActive()) + return false; + + BRect bounds(Bounds()); + BRect extent(Extent()); + + bool wouldScroll = false; + bool keepGoing; + float scrollIncrement; + + BRect border(bounds); + border.bottom = border.top; + border.top -= kBorderHeight; + if (ViewMode() == kListMode) + border.top -= kTitleViewHeight; + + if (bounds.top > extent.top) { + if (selectionScrolling) { + keepGoing = mouseLoc.y < bounds.top; + if (fabs(bounds.top - mouseLoc.y) > kSlowScrollBucket) + scrollIncrement = fAutoScrollInc / 1.5f; + else + scrollIncrement = fAutoScrollInc / 4; + } else { + keepGoing = border.Contains(mouseLoc); + scrollIncrement = fAutoScrollInc; + } + + if (keepGoing) { + wouldScroll = true; + if (shouldScroll) + if (fVScrollBar) + fVScrollBar->SetValue(fVScrollBar->Value() - scrollIncrement); + else + ScrollBy(0, -scrollIncrement); + } + } + + border = bounds; + border.top = border.bottom; + border.bottom += (float)B_H_SCROLL_BAR_HEIGHT; + if (bounds.bottom < extent.bottom) { + if (selectionScrolling) { + keepGoing = mouseLoc.y > bounds.bottom; + if (fabs(bounds.bottom - mouseLoc.y) > kSlowScrollBucket) + scrollIncrement = fAutoScrollInc / 1.5f; + else + scrollIncrement = fAutoScrollInc / 4; + } else { + keepGoing = border.Contains(mouseLoc); + scrollIncrement = fAutoScrollInc; + } + + if (keepGoing) { + wouldScroll = true; + if (shouldScroll) + if (fVScrollBar) + fVScrollBar->SetValue(fVScrollBar->Value() + scrollIncrement); + else + ScrollBy(0, scrollIncrement); + } + } + + border = bounds; + border.right = border.left; + border.left -= 6; + if (bounds.left > extent.left) { + if (selectionScrolling) { + keepGoing = mouseLoc.x < bounds.left; + if (fabs(bounds.left - mouseLoc.x) > kSlowScrollBucket) + scrollIncrement = fAutoScrollInc / 1.5f; + else + scrollIncrement = fAutoScrollInc / 4; + } else { + keepGoing = border.Contains(mouseLoc); + scrollIncrement = fAutoScrollInc; + } + + if (keepGoing) { + wouldScroll = true; + if (shouldScroll) + if (fHScrollBar) + fHScrollBar->SetValue(fHScrollBar->Value() - scrollIncrement); + else + ScrollBy(-scrollIncrement, 0); + } + } + + border = bounds; + border.left = border.right; + border.right += (float)B_V_SCROLL_BAR_WIDTH; + if (bounds.right < extent.right) { + if (selectionScrolling) { + keepGoing = mouseLoc.x > bounds.right; + if (fabs(bounds.right - mouseLoc.x) > kSlowScrollBucket) + scrollIncrement = fAutoScrollInc / 1.5f; + else + scrollIncrement = fAutoScrollInc / 4; + } else { + keepGoing = border.Contains(mouseLoc); + scrollIncrement = fAutoScrollInc; + } + + if (keepGoing) { + wouldScroll = true; + if (shouldScroll) + if (fHScrollBar) + fHScrollBar->SetValue(fHScrollBar->Value() + scrollIncrement); + else + ScrollBy(scrollIncrement, 0); + } + } + + return wouldScroll; +} + + +void +BPoseView::HandleAutoScroll() +{ + if (!fShouldAutoScroll) + return; + + uint32 button; + BPoint mouseLoc; + GetMouse(&mouseLoc, &button); + + if (!button) { + fAutoScrollState = kAutoScrollOff; + Window()->SetPulseRate(500000); + return; + } + + switch (fAutoScrollState) { + case kWaitForTransition: + if (CheckAutoScroll(mouseLoc, false) == false) + fAutoScrollState = kDelayAutoScroll; + break; + + case kDelayAutoScroll: + if (CheckAutoScroll(mouseLoc, false) == true) { + snooze(600000); + GetMouse(&mouseLoc, &button); + if (CheckAutoScroll(mouseLoc, false) == true) + fAutoScrollState = kAutoScrollOn; + } + break; + + case kAutoScrollOn: + CheckAutoScroll(mouseLoc, true); + break; + } +} + + +BRect +BPoseView::CalcPoseRect(BPose *pose, int32 index, bool min) const +{ + return pose->CalcRect(BPoint(0, index * fListElemHeight), + this, min); +} + + +bool +BPoseView::Represents(const node_ref *node) const +{ + return *(fModel->NodeRef()) == *node; +} + + +bool +BPoseView::Represents(const entry_ref *ref) const +{ + return *fModel->EntryRef() == *ref; +} + + +void +BPoseView::ShowBarberPole() +{ + if (fCountView) { + AutoLock lock(Window()); + if (!lock) + return; + fCountView->StartBarberPole(); + } +} + + +void +BPoseView::HideBarberPole() +{ + if (fCountView) { + AutoLock lock(Window()); + if (!lock) + return; + fCountView->EndBarberPole(); + } +} + + +bool +BPoseView::IsWatchingDateFormatChange() +{ + return fIsWatchingDateFormatChange; +} + + +void +BPoseView::StartWatchDateFormatChange() +{ + if (IsFilePanel()) { + BMessenger tracker(kTrackerSignature); + BHandler::StartWatching(tracker, kDateFormatChanged); + } else { + be_app->LockLooper(); + be_app->StartWatching(this, kDateFormatChanged); + be_app->UnlockLooper(); + } + + fIsWatchingDateFormatChange = true; +} + + +void +BPoseView::StopWatchDateFormatChange() +{ + if (IsFilePanel()) { + BMessenger tracker(kTrackerSignature); + BHandler::StopWatching(tracker, kDateFormatChanged); + } else { + be_app->LockLooper(); + be_app->StopWatching(this, kDateFormatChanged); + be_app->UnlockLooper(); + } + + fIsWatchingDateFormatChange = false; +} + + +void +BPoseView::UpdateDateColumns(BMessage *message) +{ + int32 columnCount = CountColumns(); + + BRect columnRect(Bounds()); + + if (IsFilePanel()) { + FormatSeparator separator; + DateOrder format; + bool clock; + + message->FindInt32("TimeFormatSeparator", (int32*)&separator); + message->FindInt32("DateOrderFormat", (int32*)&format); + message->FindBool("24HrClock", &clock); + + TrackerSettings settings; + settings.SetTimeFormatSeparator(separator); + settings.SetDateOrderFormat(format); + settings.SetClockTo24Hr(clock); + } + + for (int32 i = 0; i < columnCount; i++) { + BColumn *col = ColumnAt(i); + if (col && col->AttrType() == B_TIME_TYPE) { + columnRect.left = col->Offset(); + columnRect.right = columnRect.left + col->Width(); + DrawViewCommon(columnRect, true); // true means recalculate texts. + } + } +} + + +void +BPoseView::AdaptToVolumeChange(BMessage *) +{ +} + + +void +BPoseView::AdaptToDesktopIntegrationChange(BMessage *) +{ +} + + +bool +BPoseView::EraseWidgetTextBackground() const +{ + return fEraseWidgetBackground; +} + + +void +BPoseView::SetEraseWidgetTextBackground(bool on) +{ + fEraseWidgetBackground = on; +} + + +/* static */ +bool +BPoseView::ShouldIntegrateDesktop(const BVolume &volume) +{ + if (!volume.IsPersistent()) + return false; + + TrackerSettings settings; + if (settings.IntegrateAllNonBootDesktops()) + return true; + + if (!settings.IntegrateNonBootBeOSDesktops()) + return false; + + // That's obviously what makes a BeOS desktop :-) + return volume.KnowsQuery() && volume.KnowsAttr() && volume.KnowsMime(); +} + + +// #pragma mark - + + +BHScrollBar::BHScrollBar(BRect bounds, const char *name, BView *target) + : BScrollBar(bounds, name, target, 0, 1, B_HORIZONTAL), + fTitleView(0) +{ +} + + +void +BHScrollBar::ValueChanged(float value) +{ + if (fTitleView) { + BPoint origin = fTitleView->LeftTop(); + fTitleView->ScrollTo(BPoint(value, origin.y)); + } + + _inherited::ValueChanged(value); +} + + +TPoseViewFilter::TPoseViewFilter(BPoseView *pose) + : BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE), + fPoseView(pose) +{ +} + + +TPoseViewFilter::~TPoseViewFilter() +{ +} + + +filter_result +TPoseViewFilter::Filter(BMessage *message, BHandler **) +{ + filter_result result = B_DISPATCH_MESSAGE; + + switch (message->what) { + case B_ARCHIVED_OBJECT: + bool handled = fPoseView->HandleMessageDropped(message); + if (handled) + result = B_SKIP_MESSAGE; + break; + } + + return result; +} + + +// static member initializations + +float BPoseView::fFontHeight = -1; +font_height BPoseView::fFontInfo = { 0, 0, 0 }; +bigtime_t BPoseView::fLastKeyTime = 0; +_BWidthBuffer_* BPoseView::fWidthBuf = new _BWidthBuffer_; +BFont BPoseView::fCurrentFont; +OffscreenBitmap *BPoseView::fOffscreen = new OffscreenBitmap; +char BPoseView::fMatchString[] = ""; + diff --git a/src/kits/tracker/PoseView.h b/src/kits/tracker/PoseView.h new file mode 100644 index 0000000000..1dd8e38bdc --- /dev/null +++ b/src/kits/tracker/PoseView.h @@ -0,0 +1,1008 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// +// BPoseView is a container for poses, handling all of the interaction, drawing, +// etc. The three different view modes are handled here. +// +// this is by far the fattest Tracker class and over time will undergo a lot of +// trimming + +#ifndef _POSE_VIEW_H +#define _POSE_VIEW_H + +#include "AttributeStream.h" +#include "ContainerWindow.h" +#include "Model.h" +#include "PendingNodeMonitorCache.h" +#include "Pose.h" +#include "PoseList.h" +#include "TitleView.h" +#include "Utilities.h" +#include "ViewState.h" + +#include +#include +#include +#include +#include +#include +#include + +class BRefFilter; +class BList; + +// TODO: Get rid of this. +class _BWidthBuffer_; + +namespace BPrivate { + +class BCountView; +class BContainerWindow; +class BHScrollBar; +class EntryListBase; + +const int32 kSmallStep = 10; +const int32 kListOffset = 20; + +const uint32 kMiniIconMode = 'Tmic'; +const uint32 kIconMode = 'Ticn'; +const uint32 kListMode = 'Tlst'; + +const uint32 kCheckTypeahead = 'Tcty'; + +class BPoseView : public BView { + public: + BPoseView(Model *, BRect, uint32 viewMode, uint32 resizeMask = B_FOLLOW_ALL); + virtual ~BPoseView(); + + // setup, teardown + virtual void Init(AttributeStreamNode *); + virtual void Init(const BMessage &); + void InitCommon(); + virtual void DetachedFromWindow(); + + // Returns true if for instance, node ref is a remote desktop directory and + // this is a desktop pose view. + virtual bool Represents(const node_ref *) const; + virtual bool Represents(const entry_ref *) const; + + BContainerWindow *ContainerWindow() const; + Model *TargetModel() const; + + virtual bool IsFilePanel() const; + bool IsDesktopWindow() const; + virtual bool IsDesktopView() const; + + // state saving/restoring + virtual void SaveState(AttributeStreamNode *node); + virtual void RestoreState(AttributeStreamNode *); + virtual void RestoreColumnState(AttributeStreamNode *); + void AddColumnList(BObjectList *list); + virtual void SaveColumnState(AttributeStreamNode *); + virtual void SavePoseLocations(BRect *frameIfDesktop = 0); + void DisableSaveLocation(); + + virtual void SaveState(BMessage &) const; + virtual void RestoreState(const BMessage &); + virtual void RestoreColumnState(const BMessage &); + virtual void SaveColumnState(BMessage &) const; + + bool StateNeedsSaving(); + + // switch between mini icon mode, icon mode and list mode + virtual void SetViewMode(uint32 mode); + uint32 ViewMode() const; + + // re-use the pose view for a new directory + virtual void SwitchDir(const entry_ref *, AttributeStreamNode *node = 0); + + // in the rare cases where a pose view needs to be explicitly refreshed + // (for instance in a query window with a dynamic date query), this is + // used + virtual void Refresh(); + + // callbacks + virtual void MessageReceived(BMessage *); + virtual void AttachedToWindow(); + virtual void WindowActivated(bool); + virtual void MakeFocus(bool = true); + virtual void MouseMoved(BPoint, uint32, const BMessage *); + virtual void Draw(BRect update_rect); + virtual void MouseDown(BPoint where); + virtual void KeyDown(const char *, int32); + virtual void Pulse(); + virtual void MoveBy(float, float); + + // misc. mode setters + void SetMultipleSelection(bool); + void SetDragEnabled(bool); + void SetDropEnabled(bool); + void SetSelectionRectEnabled(bool); + void SetAlwaysAutoPlace(bool); + void SetSelectionChangedHook(bool); + void SetShowHideSelection(bool); + void SetEnsurePosesVisible(bool); + void SetIconMapping(bool); + void SetAutoScroll(bool); + void SetPoseEditing(bool); + + void UpdateVolumeIcon(dev_t device, bool forceUpdate = false); + void UpdateVolumeIcons(); + + // file change notification handler + virtual bool FSNotification(const BMessage *); + + // scrollbars + virtual void UpdateScrollRange(); + virtual void SetScrollBarsTo(BPoint); + virtual void AddScrollBars(); + BHScrollBar *HScrollBar() const; + BScrollBar *VScrollBar() const ; + void DisableScrollBars(); + void EnableScrollBars(); + + // sorting + virtual void SortPoses(); + void SetPrimarySort(uint32 attrHash); + void SetSecondarySort(uint32 attrHash); + void SetReverseSort(bool reverse); + uint32 PrimarySort() const; + uint32 PrimarySortType() const; + uint32 SecondarySort() const; + uint32 SecondarySortType() const; + bool ReverseSort() const; + void CheckPoseSortOrder(BPose *, int32 index); + void CheckPoseVisibility(BRect * = NULL); + // make sure pose fits the screen and/or window bounds if needed + + // view metrics + font_height FontInfo() const; + // returns height, descent, etc. + float FontHeight() const; + float ListElemHeight() const; + + void SetIconPoseHeight(); + float IconPoseHeight() const; + + BRect Extent() const; + void GetLayoutInfo(uint32 viewMode, BPoint *grid, BPoint *offset) const; + + int32 CountItems() const; + void UpdateCount(); + + rgb_color DeskTextColor() const; + rgb_color DeskTextBackColor() const; + + bool EraseWidgetTextBackground() const; + void SetEraseWidgetTextBackground(bool); + // used to not erase when we have a background image and + // invalidate instead + + // column handling + void ColumnRedraw(BRect updateRect); + bool AddColumn(BColumn *, const BColumn *after = NULL); + bool RemoveColumn(BColumn *column, bool runAlert); + void MoveColumnTo(BColumn *src, BColumn *dest); + bool ResizeColumnToWidest(BColumn *column); + BPoint ResizeColumn(BColumn *, float, float *lastLineDrawPos = NULL, + void (*drawLineFunc)(BPoseView *, BPoint, BPoint) = 0, + void (*undrawLineFunc)(BPoseView *, BPoint, BPoint) = 0); + // returns the bottom right of the last pose drawn or bottom right of + // bounds + + BColumn *ColumnAt(int32 index) const; + BColumn *ColumnFor(uint32 attribute_hash) const; + BColumn *FirstColumn() const; + BColumn *LastColumn() const; + int32 IndexOfColumn(const BColumn *) const; + int32 CountColumns() const; + + // pose access + int32 IndexOfPose(const BPose *) const; + BPose *PoseAtIndex(int32 index) const; + + BPose *FindPose(BPoint where, int32 *index = NULL) const; + // return pose at location h, v (search list starting from bottom so + // drawing and hit detection reflect the same pose ordering) + BPose *FindPose(const Model *, int32 *index = NULL) const; + BPose *FindPose(const node_ref *, int32 *index = NULL) const; + BPose *FindPose(const entry_ref *, int32 *index = NULL) const; + BPose *FindPose(const entry_ref *, int32 specifierForm, int32 *index) const; + // special form of FindPose used for scripting, may + // ask for previous or next pose + BPose *DeepFindPose(const node_ref *node, int32 *index = NULL) const; + // same as FindPose, node can be a target of the actual + // pose if the pose is a symlink + + void OpenInfoWindows(); + void SetDefaultPrinter(); + + void IdentifySelection(); + void UnmountSelectedVolumes(); + virtual void OpenParent(); + + virtual void OpenSelection(BPose *clicked_pose = NULL, int32 *index = NULL); + void OpenSelectionUsing(BPose *clicked_pose = NULL, int32 *index = NULL); + // launches the open with window + virtual void MoveSelectionTo(BPoint, BPoint, BContainerWindow *); + void DuplicateSelection(BPoint *dropStart = NULL, BPoint *dropEnd = NULL); + + // Move to trash calls try to select the next pose in the view when they + // are dones + virtual void MoveSelectionToTrash(bool selectNext = true); + virtual void DeleteSelection(bool selectNext = true, bool askUser = true); + virtual void MoveEntryToTrash(const entry_ref *, bool selectNext = true); + + void RestoreSelectionFromTrash(bool selectNext = true); + + // selection + PoseList *SelectionList() const; + void SelectAll(); + void InvertSelection(); + int32 SelectMatchingEntries(const BMessage *); + void ShowSelectionWindow(); + void ClearSelection(); + void ShowSelection(bool); + void AddRemovePoseFromSelection(BPose *pose, int32 index, bool select); + + BLooper *SelectionHandler(); + void SetSelectionHandler(BLooper *); + + BObjectList *MimeTypesInSelection(); + + // pose selection + void SelectPose(BPose *, int32 index, bool scrollIntoView = true); + void AddPoseToSelection(BPose *, int32 index, bool scrollIntoView = true); + void RemovePoseFromSelection(BPose *); + void SelectPoseAtLocation(BPoint); + void SelectPoses(int32 start, int32 end); + + // pose handling + void ScrollIntoView(BPose *pose, int32 index, bool drawOnly = false); + void SetActivePose(BPose *); + BPose *ActivePose() const; + void CommitActivePose(bool saveChanges = true); + static bool PoseVisible(const Model *, const PoseInfo *, bool inFilePanel); + bool FrameForPose(BPose *targetpose, bool convert, BRect *poseRect); + bool CreateSymlinkPoseTarget(Model *symlink); + // used to complete a symlink pose; returns true if + // target symlink should not be shown + void ResetPosePlacementHint(); + void PlaceFolder(const entry_ref *, const BMessage *); + + // clipboard handling for poses + bool HasPosesInClipboard(); + void SetHasPosesInClipboard(bool hasPoses); + void SetPosesClipboardMode(uint32 clipboardMode); + void UpdatePosesClipboardModeFromClipboard(BMessage *clipboardReport = NULL); + + // filtering + void SetRefFilter(BRefFilter *); + BRefFilter *RefFilter() const; + + // access for mime types represented in the pose view + const char *MimeTypeAt(int32); + int32 CountMimeTypes(); + void RefreshMimeTypeList(); + + // drag&drop handling + virtual bool HandleMessageDropped(BMessage *); + static bool HandleDropCommon(BMessage *dragMessage, Model *target, BPose *, + BView *view, BPoint dropPt); + // used by pose views and info windows + static bool CanHandleDragSelection(const Model *target, + const BMessage *dragMessage, bool ignoreTypes); + virtual void DragSelectedPoses(const BPose *clickedPose, BPoint); + virtual void DragSelectionRect(BPoint, bool extendSelection); + + void MoveSelectionInto(Model *destFolder, BContainerWindow *srcWindow, + bool forceCopy, bool createLink = false, bool relativeLink = false); + static void MoveSelectionInto(Model *destFolder, BContainerWindow *srcWindow, + BContainerWindow *destWindow, uint32 buttons, BPoint loc, + bool forceCopy, bool createLink = false, bool relativeLink = false); + + bool UpdateDropTarget(BPoint, const BMessage *, bool trackingContextMenu); + // return true if drop target changed + void HiliteDropTarget(bool hiliteState); + + void DragStop(); + // throw away cached up structures + + static bool MenuTrackingHook(BMenu *menu, void *castToThis); + // hook for spring loaded nav-menus + + // scripting + virtual BHandler *ResolveSpecifier(BMessage *message, int32 index, + BMessage *specifier, int32 form, const char *property); + virtual status_t GetSupportedSuites(BMessage *); + + // string width calls that use local width caches, faster than usin + // the general purpose BView::StringWidth + float StringWidth(const char *) const; + float StringWidth(const char *, int32) const; + // deliberately hide the BView StringWidth here - this makes it + // easy to have the right StringWidth picked up by + // template instantiation, as used by WidgetAttributeText + + // show/hide barberpole while a background task is filling up the view, etc. + void ShowBarberPole(); + void HideBarberPole(); + + bool fShowSelectionWhenInactive; + bool fTransparentSelection; + bool fIsDrawingSelectionRect; + + bool IsWatchingDateFormatChange(); + void StartWatchDateFormatChange(); + void StopWatchDateFormatChange(); + + void UpdateDateColumns(BMessage *); + virtual void AdaptToVolumeChange(BMessage *); + virtual void AdaptToDesktopIntegrationChange(BMessage *); + + protected: + // view setup + virtual void SetUpDefaultColumnsIfNeeded(); + + virtual EntryListBase *InitDirentIterator(const entry_ref *); + // sets up an entry iterator for _add_poses_ + // overriden by QueryPoseView, etc. to provide different iteration + + void Cleanup(bool doAll = false); + // clean up poses + void NewFolder(const BMessage *); + // create a new folder, optionally specify a location + + void NewFileFromTemplate(const BMessage *); + // create a new file based on a template, optionally specify a location + + void ShowContextMenu(BPoint); + + // scripting handlers + virtual bool HandleScriptingMessage(BMessage *message); + bool SetProperty(BMessage *message, BMessage *specifier, int32 form, + const char *property, BMessage *reply); + bool GetProperty(BMessage *, int32, const char *, BMessage *); + bool CreateProperty(BMessage *message, BMessage *specifier, int32, + const char *, BMessage *reply); + bool ExecuteProperty(BMessage *specifier, int32, const char *, BMessage *reply); + bool CountProperty(BMessage *, int32, const char *, BMessage *); + bool DeleteProperty(BMessage *, int32, const char *, BMessage *); + + void ClearPoses(); + // remove all the current poses from the view + + // pose info read/write calls + void ReadPoseInfo(Model *, PoseInfo *); + ExtendedPoseInfo *ReadExtendedPoseInfo(Model *); + + // pose creation + BPose *EntryCreated(const node_ref *, const node_ref *, const char *, int32 *index = 0); + + BPose *CreatePose(Model *, PoseInfo *, bool insertionSort = true, + int32 *index = 0, BRect *boundsPtr = 0, bool forceDraw = true); + virtual void CreatePoses(Model **models, PoseInfo *poseInfoArray, int32 count, + BPose **resultingPoses, bool insertionSort = true, int32 *lastPoseIndexPtr = 0, + BRect *boundsPtr = 0, bool forceDraw = false); + virtual bool ShouldShowPose(const Model *, const PoseInfo *); + // filter, subclasses override to control which poses show up + // subclasses should always call inherited + void CreateVolumePose(BVolume *, bool watchIndividually); + + virtual bool AddPosesThreadValid(const entry_ref *) const; + // verifies whether or not the current set of AddPoses threads + // are valid and allowed to be adding poses -- returns false + // in the case where the directory has been switched while populating + // the view + + virtual void AddPoses(Model *model = NULL); + // if is zero, PoseView has other means of iterating through all + // the entries that it adds + virtual void AddRootPoses(bool watchIndividually, bool mountShared); + // watchIndividually is used when placing a volume pose onto the Desktop + // where unlike in the Root window it will not be watched by the folder + // representing root. If set, each volume will therefore be watched + // individually + virtual void RemoveRootPoses(); + virtual void AddTrashPoses(); + + virtual bool DeletePose(const node_ref *, BPose *pose = NULL, int32 index = 0); + virtual void DeleteSymLinkPoseTarget(const node_ref *itemNode, BPose *pose, + int32 index); + // the pose itself wasn't deleted but it's target node was - the + // pose must be a symlink + static void PoseHandleDeviceUnmounted(BPose *pose, Model *model, int32 index, + BPoseView *poseView, dev_t device); + static void RemoveNonBootDesktopModels(BPose *, Model *model, int32, + BPoseView *poseView, dev_t); + + // pose placement + void CheckAutoPlacedPoses(); + // find poses that need placing and place them in a new spot + void PlacePose(BPose *, BRect &); + // find a new place for a pose, starting at fHintLocation and place it + bool SlotOccupied(BRect poseRect, BRect viewBounds) const; + void NextSlot(BPose *, BRect &poseRect, BRect viewBounds); + void TrySettingPoseLocation(BNode *node, BPoint point); + BPoint PinToGrid(BPoint, BPoint grid, BPoint offset) const; + + // zombie pose handling + Model *FindZombie(const node_ref *, int32 *index = 0); + BPose *ConvertZombieToPose(Model *zombie, int32 index); + + // pose handling + BRect CalcPoseRect(BPose *, int32 index, bool minimal = false) const; + void DrawPose(BPose *, int32 index, bool fullDraw = true); + void DrawViewCommon(BRect, bool recalculateText = false); + + // pose list handling + int32 BSearchList(const BPose *, int32 *index); + void InsertPoseAfter(BPose *pose, int32 *index, int32 orientation, + BRect *invalidRect); + // does a CopyBits to scroll poses making room for a new pose, + // returns rectangle that needs invalidating + void CloseGapInList(BRect *invalidRect); + int32 FirstIndexAtOrBelow(int32 y, bool constrainIndex = true) const; + void AddToVSList(BPose *); + int32 RemoveFromVSList(const BPose *); + BPose *FindNearbyPose(char arrow, int32 *index); + BPose *FindBestMatch(int32 *index); + BPose *FindNextMatch(int32 *index, bool reverse = false); + + // node monitoring calls + virtual void StartWatching(); + virtual void StopWatching(); + + status_t WatchNewNode(const node_ref *item); + // the above would ideally be the only call of these three and it would + // be a virtual, overriding the specific watch mask in query pose view, etc. + // however we need to call WatchNewNode from inside AddPosesTask while + // the window is unlocked - we have to use the static and a cached + // messenger and masks. + static status_t WatchNewNode(const node_ref *, uint32, BMessenger); + virtual uint32 WatchNewNodeMask(); + // override to change different watch modes for query pose view, etc. + + // drag&drop handling + static bool EachItemInDraggedSelection(const BMessage *message, + bool (*)(BPose *, BPoseView *, void *), BPoseView *poseView, + void * = NULL); + // iterates through each pose in current selectiond in the source + // window of the current drag message; locks the window + // add const version + BRect GetDragRect(int32 clickedPoseIndex); + BBitmap *MakeDragBitmap(BRect dragRect, BPoint clickedPoint, int32 clickedPoseIndex, BPoint &offset); + static bool FindDragNDropAction(const BMessage *dragMessage, bool &canCopy, + bool &canMove, bool &canLink, bool &canErase); + + static bool CanTrashForeignDrag(const Model *); + static bool CanCopyOrMoveForeignDrag(const Model *, const BMessage *); + static bool DragSelectionContains(const BPose *target, const BMessage *dragMessage); + static status_t CreateClippingFile(BPoseView *poseView, BFile &result, + char *resultingName, BDirectory *dir, BMessage *message, const char *fallbackName, + bool setLocation = false, BPoint dropPoint = BPoint(0, 0)); + + // opening files, lanunching + void OpenSelectionCommon(BPose *, int32 *, bool); + // used by OpenSelection and OpenSelectionUsing + static void LaunchAppWithSelection(Model *, const BMessage *, bool checkTypes = true); + + // node monitoring calls + virtual bool EntryMoved(const BMessage *); + virtual bool AttributeChanged(const BMessage *); + virtual bool NoticeMetaMimeChanged(const BMessage *); + virtual void MetaMimeChanged(const char *, const char *); + + // click handling + bool WasDoubleClick(const BPose *, BPoint); + bool WasClickInPath(const BPose *, int32 index, BPoint) const; + int32 WaitForMouseUpOrDrag(BPoint start); + + // selection + void SelectPosesListMode(BRect, BList **); + void SelectPosesIconMode(BRect, BList **); + void AddRemoveSelectionRange(BPoint where, bool extendSelection, BPose *); + + // view drawing + void SynchronousUpdate(BRect, bool clip = false); + + // scrolling + void HandleAutoScroll(); + bool CheckAutoScroll(BPoint mouseLoc, bool shouldScroll, bool selectionScrolling = false); + + // view extent handling + void RecalcExtent(); + void AddToExtent(const BRect &); + void ClearExtent(); + void RemoveFromExtent(const BRect &); + + virtual void EditQueries(); + virtual void AddCountView(); + + void AddMimeType(const char *); + void HandleAttrMenuItemSelected(BMessage *); + void TryUpdatingBrokenLinks(); + // ran a little after a volume gets mounted + + void MapToNewIconMode(BPose *, BPoint oldGrid, BPoint oldOffset); + void ResetOrigin(); + void PinPointToValidRange(BPoint &); + // used to ensure pose locations make sense after getting them + // in pose info from attributes, etc. + + void FinishPendingScroll(float &listViewScrollBy, BRect bounds); + // utility call for CreatePoses + + // background AddPoses task calls + static status_t AddPosesTask(void *); + virtual void AddPosesCompleted(); + bool IsValidAddPosesThread(thread_id) const; + + // misc + BList *GetDropPointList(BPoint dropPoint, BPoint startPoint, const PoseList *, + bool sourceInListMode, bool dropOnGrid) const; + void SendSelectionAsRefs(uint32 what, bool onlyQueries = false); + void MoveListToTrash(BObjectList *, bool selectNext, bool deleteDirectly); + void Delete(BObjectList *, bool selectNext, bool askUser); + void Delete(const entry_ref &ref, bool selectNext, bool askUser); + void RestoreItemsFromTrash(BObjectList *, bool selectNext); + + static bool ShouldIntegrateDesktop(const BVolume &volume); + + private: + void DrawOpenAnimation(BRect); + + void MoveSelectionOrEntryToTrash(const entry_ref *ref, bool selectNext); + + protected: + BHScrollBar *fHScrollBar; + BScrollBar *fVScrollBar; + Model *fModel; + BPose *fActivePose; + BRect fExtent; + // the following should probably be just member lists, not pointers + PoseList *fPoseList; + PoseList *fVSPoseList; + PoseList *fSelectionList; + BObjectList fMimeTypesInSelectionCache; + // used for mime string based icon highliting during a drag + BObjectList *fZombieList; + PendingNodeMonitorCache pendingNodeMonitorCache; + BObjectList *fColumnList; + BObjectList *fMimeTypeList; + bool fMimeTypeListIsDirty; + BViewState *fViewState; + bool fStateNeedsSaving; + BCountView *fCountView; + float fListElemHeight; + float fIconPoseHeight; + BRegion *fUpdateRegion; + BPose *fDropTarget; + bool fDropTargetWasSelected; + BLooper *fSelectionHandler; + BPoint fLastClickPt; + bigtime_t fLastClickTime; + const BPose *fLastClickedPose; + BPoint fLastLeftTop; + BRect fLastExtent; + BTitleView *fTitleView; + BRefFilter *fRefFilter; + BPoint fGrid; + BPoint fOffset; + BPoint fHintLocation; + float fAutoScrollInc; + int32 fAutoScrollState; + std::set fAddPosesThreads; + bool fEraseWidgetBackground; + const BPose *fSelectionPivotPose; + const BPose *fRealPivotPose; + BMessageRunner *fKeyRunner; + + bool fSelectionVisible : 1; + bool fMultipleSelection : 1; + bool fDragEnabled : 1; + bool fDropEnabled : 1; + bool fSelectionRectEnabled : 1; + bool fAlwaysAutoPlace : 1; + bool fAllowPoseEditing : 1; + bool fSelectionChangedHook : 1; // get rid of this + bool fSavePoseLocations : 1; + bool fShowHideSelection : 1; + bool fOkToMapIcons : 1; + bool fEnsurePosesVisible : 1; + bool fShouldAutoScroll : 1; + bool fIsDesktopWindow : 1; + bool fIsWatchingDateFormatChange : 1; + bool fHasPosesInClipboard : 1; + + BRect fStartFrame; + BRect fSelectionRect; + + static float fFontHeight; + static font_height fFontInfo; + static BFont fCurrentFont; + static bigtime_t fLastKeyTime; + static char fMatchString[B_FILE_NAME_LENGTH]; + // used for typeahead - should be replaced by a typeahead state + + // TODO: Get rid of this. + static _BWidthBuffer_ *fWidthBuf; + + static OffscreenBitmap *fOffscreen; + + typedef BView _inherited; +}; + + +class BHScrollBar : public BScrollBar { + public: + BHScrollBar(BRect, const char *, BView *); + void SetTitleView(BView *); + + // BScrollBar overrides + virtual void ValueChanged(float); + + private: + BView *fTitleView; + + typedef BScrollBar _inherited; +}; + + +class TPoseViewFilter : public BMessageFilter { + public: + TPoseViewFilter(BPoseView *pose); + ~TPoseViewFilter(); + + filter_result Filter(BMessage *, BHandler **); + + private: + filter_result ObjectDropFilter(BMessage *, BHandler **); + + BPoseView *fPoseView; +}; + + +extern bool +ClearViewOriginOne(const char *name, uint32 type, off_t size, void *data, void *params); + +// inlines follow + +inline BContainerWindow * +BPoseView::ContainerWindow() const +{ + return dynamic_cast(Window()); +} + +inline Model * +BPoseView::TargetModel() const +{ + return fModel; +} + +inline float +BPoseView::ListElemHeight() const +{ + return fListElemHeight; +} + +inline float +BPoseView::IconPoseHeight() const +{ + return fIconPoseHeight; +} + +inline PoseList * +BPoseView::SelectionList() const +{ + return fSelectionList; +} + +inline BObjectList * +BPoseView::MimeTypesInSelection() +{ + return &fMimeTypesInSelectionCache; +} + +inline BHScrollBar* +BPoseView::HScrollBar() const +{ + return fHScrollBar; +} + +inline BScrollBar* +BPoseView::VScrollBar() const +{ + return fVScrollBar; +} + +inline bool +BPoseView::StateNeedsSaving() +{ + return fStateNeedsSaving || fViewState->StateNeedsSaving(); +} + +inline uint32 +BPoseView::ViewMode() const +{ + return fViewState->ViewMode(); +} + +inline font_height +BPoseView::FontInfo() const +{ + return fFontInfo; +} + +inline float +BPoseView::FontHeight() const +{ + return fFontHeight; +} + +inline BPose * +BPoseView::ActivePose() const +{ + return fActivePose; +} + +inline void +BPoseView::DisableSaveLocation() +{ + fSavePoseLocations = false; +} + +inline bool +BPoseView::IsFilePanel() const +{ + return false; +} + +inline bool +BPoseView::IsDesktopWindow() const +{ + return fIsDesktopWindow; +} + +inline bool +BPoseView::IsDesktopView() const +{ + return false; +} + +inline uint32 +BPoseView::PrimarySort() const +{ + return fViewState->PrimarySort(); +} + +inline uint32 +BPoseView::PrimarySortType() const +{ + return fViewState->PrimarySortType(); +} + +inline uint32 +BPoseView::SecondarySort() const +{ + return fViewState->SecondarySort(); +} + +inline uint32 +BPoseView::SecondarySortType() const +{ + return fViewState->SecondarySortType(); +} + +inline bool +BPoseView::ReverseSort() const +{ + return fViewState->ReverseSort(); +} + +inline void +BPoseView::SetShowHideSelection(bool on) +{ + fShowHideSelection = on; +} + +inline void +BPoseView::SetIconMapping(bool on) +{ + fOkToMapIcons = on; +} + +inline void +BPoseView::AddToExtent(const BRect &rect) +{ + fExtent = fExtent | rect; +} + +inline void +BPoseView::ClearExtent() +{ + fExtent.Set(LONG_MAX, LONG_MAX, LONG_MIN, LONG_MIN); +} + +inline int32 +BPoseView::CountColumns() const +{ + return fColumnList->CountItems(); +} + +inline int32 +BPoseView::IndexOfColumn(const BColumn* column) const +{ + return fColumnList->IndexOf(const_cast(column)); +} + +inline int32 +BPoseView::IndexOfPose(const BPose *pose) const +{ + return fPoseList->IndexOf(pose); +} + +inline BPose * +BPoseView::PoseAtIndex(int32 index) const +{ + return fPoseList->ItemAt(index); +} + +inline BColumn * +BPoseView::ColumnAt(int32 index) const +{ + return fColumnList->ItemAt(index); +} + +inline BColumn * +BPoseView::FirstColumn() const +{ + return fColumnList->FirstItem(); +} + +inline BColumn * +BPoseView::LastColumn() const +{ + return fColumnList->LastItem(); +} + +inline int32 +BPoseView::CountItems() const +{ + return fPoseList->CountItems(); +} + +inline void +BPoseView::SetMultipleSelection(bool state) +{ + fMultipleSelection = state; +} + +inline void +BPoseView::SetSelectionChangedHook(bool state) +{ + fSelectionChangedHook = state; +} + +inline void +BPoseView::SetAutoScroll(bool state) +{ + fShouldAutoScroll = state; +} + +inline void +BPoseView::SetPoseEditing(bool state) +{ + fAllowPoseEditing = state; +} + +inline void +BPoseView::SetDragEnabled(bool state) +{ + fDragEnabled = state; +} + +inline void +BPoseView::SetDropEnabled(bool state) +{ + fDropEnabled = state; +} + +inline void +BPoseView::SetSelectionRectEnabled(bool state) +{ + fSelectionRectEnabled = state; +} + +inline void +BPoseView::SetAlwaysAutoPlace(bool state) +{ + fAlwaysAutoPlace = state; +} + +inline void +BPoseView::SetEnsurePosesVisible(bool state) +{ + fEnsurePosesVisible = state; +} + +inline void +BPoseView::SetSelectionHandler(BLooper *looper) +{ + fSelectionHandler = looper; +} + +inline void +BPoseView::SetRefFilter(BRefFilter *filter) +{ + fRefFilter = filter; +} + +inline BRefFilter * +BPoseView::RefFilter() const +{ + return fRefFilter; +} + +inline void +BHScrollBar::SetTitleView(BView *view) +{ + fTitleView = view; +} + +inline BPose * +BPoseView::FindPose(const Model *model, int32 *index) const +{ + return fPoseList->FindPose(model, index); +} + +inline BPose * +BPoseView::FindPose(const node_ref *node, int32 *index) const +{ + return fPoseList->FindPose(node, index); +} + +inline BPose * +BPoseView::FindPose(const entry_ref *entry, int32 *index) const +{ + return fPoseList->FindPose(entry, index); +} + + +} // namespace BPrivate + +using namespace BPrivate; + +#endif /* _POSE_VIEW_H */ diff --git a/src/kits/tracker/PoseViewScripting.cpp b/src/kits/tracker/PoseViewScripting.cpp new file mode 100644 index 0000000000..5719366a6a --- /dev/null +++ b/src/kits/tracker/PoseViewScripting.cpp @@ -0,0 +1,764 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// PoseView scripting interface + +#include +#include +#include +#include + +#include +#include +#include + +#include "Tracker.h" +#include "PoseView.h" + +#define kPosesSuites "suite/vnd.Be-TrackerPoses" + +#define kPropertyPath "Path" + +#ifndef _SCRIPTING_ONLY + #if _SUPPORTS_FEATURE_SCRIPTING + #define _SCRIPTING_ONLY(x) x + #else + #define _SCRIPTING_ONLY(x) + #endif +#endif + +// notes on PoseView scripting interface: +// Indices and entry_refs are used to specify poses; In the case of indices +// and previous/next specifiers the current PoseView sort order is used. +// If PoseView is not in list view mode, the order in which poses are indexed +// is arbitrary. +// Both of these specifiers, but indices more so, are likely to be accurate only +// till a next change to the PoseView (a change may be adding, removing a pose, changing +// an attribute or stat resulting in a sort ordering change, changing the sort ordering +// rule. When getting a selected item, there is no guarantee that the item will still +// be selected after the operation. The client must be able to deal with these +// inaccuracies. +// Specifying an index/entry_ref that no longer exists will be handled well. + +#if 0 +doo Tracker get Suites of Poses of Window test +doo Tracker get Path of Poses of Window test +doo Tracker count Entry of Poses of Window test +doo Tracker get Entry of Poses of Window test +doo Tracker get Entry 2 of Poses of Window test +doo Tracker count Selection of Poses of Window test +doo Tracker get Selection of Poses of Window test +doo Tracker delete Entry 'test/6L6' of Poses of Window test +doo Tracker execute Entry 'test/6L6' of Poses of Window test +doo Tracker execute Entry 2 of Poses of Window test +doo Tracker set Selection of Poses of Window test to [0,2] +doo Tracker set Selection of Poses of Window test to 'test/KT55' +doo Tracker create Selection of Poses of Window test to 'test/EL34' +doo Tracker delete Selection 'test/EL34' of Poses of Window test +#endif + +// ToDo: +// access list view column state +// access poses +// - pose location +// - pose text widgets + + +#if _SUPPORTS_FEATURE_SCRIPTING + +const property_info kPosesPropertyList[] = { + { kPropertyPath, + { B_GET_PROPERTY }, + { B_DIRECT_SPECIFIER }, + "get Path of ... # returns the path of a Tracker window, " + "error if no path associated", + 0, + { B_REF_TYPE }, + {}, + {} + }, + { kPropertyEntry, + { B_COUNT_PROPERTIES }, + { B_DIRECT_SPECIFIER }, + "count Entry of ... # count entries in a PoseView", + 0, + { B_INT32_TYPE }, + {}, + {} + }, + { kPropertyEntry, + { B_DELETE_PROPERTY }, + { B_ENTRY_SPECIFIER, B_INDEX_SPECIFIER }, + "delete Entry {path|index} # deletes specified entries in a PoseView", + 0, + {}, + {}, + {} + }, + { kPropertyEntry, + { B_GET_PROPERTY }, + { B_DIRECT_SPECIFIER, B_INDEX_SPECIFIER, kPreviousSpecifier, kNextSpecifier }, + "get Entry [next|previous|index] # returns specified entries", + 0, + { B_REF_TYPE }, + {}, + {} + }, + { kPropertyEntry, + { B_EXECUTE_PROPERTY }, + { B_ENTRY_SPECIFIER, B_INDEX_SPECIFIER }, + "execute Entry {path|index} # opens specified entries", + 0, + { B_REF_TYPE }, + {}, + {} + }, + { kPropertySelection, + { B_GET_PROPERTY }, + { B_DIRECT_SPECIFIER, kPreviousSpecifier, kNextSpecifier }, + "get Selection [next|previous] # returns the selected entries", + 0, + { B_REF_TYPE }, + {}, + {} + }, + { kPropertySelection, + { B_SET_PROPERTY }, + { B_DIRECT_SPECIFIER, kPreviousSpecifier, kNextSpecifier }, + "set Selection of ... to {next|previous|entry} # selects specified entries", + 0, + {}, + {}, + {} + }, + { kPropertySelection, + { B_COUNT_PROPERTIES }, + { B_DIRECT_SPECIFIER }, + "count Selection of ... # counts selected items", + 0, + { B_INT32_TYPE }, + {}, + {} + }, + { kPropertySelection, + { B_CREATE_PROPERTY }, + { B_DIRECT_SPECIFIER }, + "create selection of ... to {entry|index} " + "# adds specified items to a selection in a PoseView", + 0, + {}, + {}, + {} + }, + { kPropertySelection, + { B_DELETE_PROPERTY }, + { B_ENTRY_SPECIFIER, B_INDEX_SPECIFIER }, + "delete selection {path|index} of ... " + "# removes specified items from a selection in a PoseView", + 0, + {}, + {}, + {} + }, + {NULL, + {}, + {}, + NULL, 0, + {}, + {}, + {} + } +}; + +#endif + +status_t +BPoseView::GetSupportedSuites(BMessage *_SCRIPTING_ONLY(data)) +{ +#if _SUPPORTS_FEATURE_SCRIPTING + data->AddString("suites", kPosesSuites); + BPropertyInfo propertyInfo(const_cast(kPosesPropertyList)); + data->AddFlat("messages", &propertyInfo); + + return _inherited::GetSupportedSuites(data); +#else + return B_UNSUPPORTED; +#endif +} + +bool +BPoseView::HandleScriptingMessage(BMessage *_SCRIPTING_ONLY(message)) +{ +#if _SUPPORTS_FEATURE_SCRIPTING + if (message->what != B_GET_PROPERTY + && message->what != B_SET_PROPERTY + && message->what != B_CREATE_PROPERTY + && message->what != B_COUNT_PROPERTIES + && message->what != B_DELETE_PROPERTY + && message->what != B_EXECUTE_PROPERTY) + return false; + + // dispatch scripting messages + BMessage reply(B_REPLY); + const char *property = 0; + bool handled = false; + + int32 index = 0; + int32 form = 0; + BMessage specifier; + status_t result = message->GetCurrentSpecifier(&index, &specifier, + &form, &property); + + if (result != B_OK || index == -1) + return false; + + ASSERT(property); + + switch (message->what) { + case B_CREATE_PROPERTY: + handled = CreateProperty(message, &specifier, form, property, &reply); + break; + + case B_GET_PROPERTY: + handled = GetProperty(&specifier, form, property, &reply); + break; + + case B_SET_PROPERTY: + handled = SetProperty(message, &specifier, form, property, &reply); + break; + + case B_COUNT_PROPERTIES: + handled = CountProperty(&specifier, form, property, &reply); + break; + + case B_DELETE_PROPERTY: + handled = DeleteProperty(&specifier, form, property, &reply); + break; + + case B_EXECUTE_PROPERTY: + handled = ExecuteProperty(&specifier, form, property, &reply); + break; + } + + if (handled) + // done handling message, send a reply + message->SendReply(&reply); + return handled; +#else + return false; +#endif +} + +bool +BPoseView::ExecuteProperty(BMessage *_SCRIPTING_ONLY(specifier), + int32 _SCRIPTING_ONLY(form), const char *_SCRIPTING_ONLY(property), + BMessage *_SCRIPTING_ONLY(reply)) +{ +#if _SUPPORTS_FEATURE_SCRIPTING + status_t error = B_OK; + bool handled = false; + if (strcmp(property, kPropertyEntry) == 0) { + BMessage launchMessage(B_REFS_RECEIVED); + + if (form == (int32)B_ENTRY_SPECIFIER) { + // move all poses specified by entry_ref to Trash + entry_ref ref; + for (int32 index = 0; specifier->FindRef("refs", index, &ref) + == B_OK; index++) + launchMessage.AddRef("refs", &ref); + + } else if (form == (int32)B_INDEX_SPECIFIER) { + // move all poses specified by index to Trash + int32 specifyingIndex; + for (int32 index = 0; specifier->FindInt32("index", index, + &specifyingIndex) == B_OK; index++) { + BPose *pose = PoseAtIndex(specifyingIndex); + + if (!pose) { + error = B_ENTRY_NOT_FOUND; + break; + } + + launchMessage.AddRef("refs", pose->TargetModel()->EntryRef()); + } + } else + return false; + + if (error == B_OK) { + // add a messenger to the launch message that will be used to + // dispatch scripting calls from apps to the PoseView + launchMessage.AddMessenger("TrackerViewToken", BMessenger(this, 0, 0)); + if (fSelectionHandler) + fSelectionHandler->PostMessage(&launchMessage); + } + handled = true; + } + + if (error != B_OK) + reply->AddInt32("error", error); + + return handled; +#else + return false; +#endif +} + +bool +BPoseView::CreateProperty(BMessage *_SCRIPTING_ONLY(specifier), BMessage *, + int32 _SCRIPTING_ONLY(form), const char *_SCRIPTING_ONLY(property), + BMessage *_SCRIPTING_ONLY(reply)) +{ +#if _SUPPORTS_FEATURE_SCRIPTING + status_t error = B_OK; + bool handled = false; + if (strcmp(property, kPropertySelection) == 0) { + // creating on a selection expands the current selection + + if (form != B_DIRECT_SPECIFIER) + // only support direct specifier + return false; + + // items to add to a selection may be passed as refs or as indices + if (specifier->HasRef("data")) { + entry_ref ref; + // select poses specified by entries + for (int32 index = 0; specifier->FindRef("data", index, &ref) + == B_OK; index++) { + + int32 poseIndex; + BPose *pose = FindPose(&ref, form, &poseIndex); + + if (!pose) { + error = B_ENTRY_NOT_FOUND; + handled = true; + break; + } + + AddPoseToSelection(pose, poseIndex); + } + handled = true; + } else { + // select poses specified by indices + int32 specifyingIndex; + for (int32 index = 0; specifier->FindInt32("data", index, + &specifyingIndex) == B_OK; index++) { + + BPose *pose = PoseAtIndex(specifyingIndex); + if (!pose) { + error = B_BAD_INDEX; + handled = true; + break; + } + + AddPoseToSelection(pose, specifyingIndex); + } + handled = true; + } + } + + if (error != B_OK) + reply->AddInt32("error", error); + + return handled; +#else + return false; +#endif +} + +bool +BPoseView::DeleteProperty(BMessage *_SCRIPTING_ONLY(specifier), + int32 _SCRIPTING_ONLY(form), const char *_SCRIPTING_ONLY(property), + BMessage *_SCRIPTING_ONLY(reply)) +{ +#if _SUPPORTS_FEATURE_SCRIPTING + status_t error = B_OK; + bool handled = false; + + if (strcmp(property, kPropertySelection) == 0) { + // deleting on a selection is handled as removing a part of the selection + // not to be confused with deleting a selected item + + if (form == (int32)B_ENTRY_SPECIFIER) { + entry_ref ref; + // select poses specified by entries + for (int32 index = 0; specifier->FindRef("refs", index, &ref) + == B_OK; index++) { + + int32 poseIndex; + BPose *pose = FindPose(&ref, form, &poseIndex); + + if (!pose) { + error = B_ENTRY_NOT_FOUND; + break; + } + + RemovePoseFromSelection(pose); + } + handled = true; + + } else if (form == B_INDEX_SPECIFIER) { + // move all poses specified by index to Trash + int32 specifyingIndex; + for (int32 index = 0; specifier->FindInt32("index", index, + &specifyingIndex) == B_OK; index++) { + BPose *pose = PoseAtIndex(specifyingIndex); + + if (!pose) { + error = B_BAD_INDEX; + break; + } + + RemovePoseFromSelection(pose); + } + handled = true; + } else + return false; + + } else if (strcmp(property, kPropertyEntry) == 0) { + // deleting entries is handled by moving entries to trash + + // build a list of entries, specified by the specifier + BObjectList *entryList = new BObjectList(); + // list will be deleted for us by the trashing thread + + if (form == (int32)B_ENTRY_SPECIFIER) { + // move all poses specified by entry_ref to Trash + entry_ref ref; + for (int32 index = 0; specifier->FindRef("refs", index, &ref) + == B_OK; index++) + entryList->AddItem(new entry_ref(ref)); + + } else if (form == (int32)B_INDEX_SPECIFIER) { + // move all poses specified by index to Trash + int32 specifyingIndex; + for (int32 index = 0; specifier->FindInt32("index", index, &specifyingIndex) + == B_OK; index++) { + BPose *pose = PoseAtIndex(specifyingIndex); + + if (!pose) { + error = B_BAD_INDEX; + break; + } + + entryList->AddItem(new entry_ref(*pose->TargetModel()->EntryRef())); + } + } else + return false; + + if (error == B_OK) { + TrackerSettings settings; + if (!settings.DontMoveFilesToTrash()) { + // move the list we build into trash, don't make the trashing task + // select the next item + MoveListToTrash(entryList, false, false); + } else + Delete(entryList, false, settings.AskBeforeDeleteFile()); + } + + handled = true; + } + + if (error != B_OK) + reply->AddInt32("error", error); + + return handled; +#else + return false; +#endif +} + +bool +BPoseView::CountProperty(BMessage *, int32, const char *_SCRIPTING_ONLY(property), + BMessage *_SCRIPTING_ONLY(reply)) +{ +#if _SUPPORTS_FEATURE_SCRIPTING + bool handled = false; +// PRINT(("BPoseView::CountProperty, %s\n", property)); + + // just return the respecitve counts + if (strcmp(property, kPropertySelection) == 0) { + reply->AddInt32("result", fSelectionList->CountItems()); + handled = true; + } else if (strcmp(property, kPropertyEntry) == 0) { + reply->AddInt32("result", fPoseList->CountItems()); + handled = true; + } + return handled; +#else + return false; +#endif +} + +bool +BPoseView::GetProperty(BMessage *_SCRIPTING_ONLY(specifier), + int32 _SCRIPTING_ONLY(form), const char *_SCRIPTING_ONLY(property), + BMessage *_SCRIPTING_ONLY(reply)) +{ +#if _SUPPORTS_FEATURE_SCRIPTING +// PRINT(("GetProperty %s\n", property)); + bool handled = false; + status_t error = B_OK; + + if (strcmp(property, kPropertyPath) == 0) { + if (form == B_DIRECT_SPECIFIER) { + handled = true; + if (!TargetModel()) + error = B_NOT_A_DIRECTORY; + else + reply->AddRef("result", TargetModel()->EntryRef()); + } + } else if (strcmp(property, kPropertySelection) == 0) { + int32 count = fSelectionList->CountItems(); + switch (form) { + case B_DIRECT_SPECIFIER: + // return entries of all poses in selection + for (int32 index = 0; index < count; index++) + reply->AddRef("result", fSelectionList->ItemAt(index)-> + TargetModel()->EntryRef()); + + handled = true; + break; + + case kPreviousSpecifier: + case kNextSpecifier: + { + // return entry and index of selected pose before or after + // specified pose + entry_ref ref; + if (specifier->FindRef("data", &ref) != B_OK) + break; + + int32 poseIndex; + BPose *pose = FindPose(&ref, &poseIndex); + + for (;;) { + if (form == (int32)kPreviousSpecifier) + pose = PoseAtIndex(--poseIndex); + else if (form == (int32)kNextSpecifier) + pose = PoseAtIndex(++poseIndex); + + if (!pose) { + error = B_ENTRY_NOT_FOUND; + break; + } + + if (pose->IsSelected()) { + reply->AddRef("result", pose->TargetModel()->EntryRef()); + reply->AddInt32("index", IndexOfPose(pose)); + break; + } + } + + handled = true; + break; + } + } + } else if (strcmp(property, kPropertyEntry) == 0) { + int32 count = fPoseList->CountItems(); + switch (form) { + case B_DIRECT_SPECIFIER: + // return all entries of all poses in PoseView + for (int32 index = 0; index < count; index++) + reply->AddRef("result", PoseAtIndex(index)->TargetModel()->EntryRef()); + + handled = true; + break; + case B_INDEX_SPECIFIER: + { + // return entry at index + int32 index; + if (specifier->FindInt32("index", &index) != B_OK) + break; + + if (!PoseAtIndex(index)) { + error = B_BAD_INDEX; + handled = true; + break; + } + reply->AddRef("result", PoseAtIndex(index)->TargetModel()->EntryRef()); + + handled = true; + break; + } + case kPreviousSpecifier: + case kNextSpecifier: + { + // return entry and index of pose before or after specified pose + entry_ref ref; + if (specifier->FindRef("data", &ref) != B_OK) + break; + + int32 tmp; + BPose *pose = FindPose(&ref, form, &tmp); + + if (!pose) { + error = B_ENTRY_NOT_FOUND; + handled = true; + break; + } + + reply->AddRef("result", pose->TargetModel()->EntryRef()); + reply->AddInt32("index", IndexOfPose(pose)); + + handled = true; + break; + } + } + } + + if (error != B_OK) + reply->AddInt32("error", error); + + return handled; +#else + return false; +#endif +} + +bool +BPoseView::SetProperty(BMessage *_SCRIPTING_ONLY(message), BMessage *, + int32 _SCRIPTING_ONLY(form), const char *_SCRIPTING_ONLY(property), + BMessage *_SCRIPTING_ONLY(reply)) +{ +#if _SUPPORTS_FEATURE_SCRIPTING + status_t error = B_OK; + bool handled = false; + + if (strcmp(property, kPropertySelection) == 0) { + entry_ref ref; + + switch (form) { + case B_DIRECT_SPECIFIER: + { + int32 selStart; + int32 selEnd; + if (message->FindInt32("data", 0, &selStart) == B_OK + && message->FindInt32("data", 1, &selEnd) == B_OK) { + + if (selStart < 0 || selStart >= fPoseList->CountItems() + || selEnd < 0 || selEnd >= fPoseList->CountItems()) { + error = B_BAD_INDEX; + handled = true; + break; + } + + SelectPoses(selStart, selEnd); + handled = true; + break; + } + } + // fall thru + case kPreviousSpecifier: + case kNextSpecifier: + { + // PRINT(("SetProperty direct/previous/next %s\n", property)); + // select/unselect poses specified by entries + bool clearSelection = true; + for (int32 index = 0; message->FindRef("data", index, &ref) + == B_OK; index++) { + + int32 poseIndex; + BPose *pose = FindPose(&ref, form, &poseIndex); + + if (!pose) { + error = B_ENTRY_NOT_FOUND; + handled = true; + break; + } + + if (clearSelection) { + // first selected item must call SelectPose so the selection + // gets cleared first + SelectPose(pose, poseIndex); + clearSelection = false; + } else + AddPoseToSelection(pose, poseIndex); + + handled = true; + } + break; + } + } + } + + if (error != B_OK) + reply->AddInt32("error", error); + + return handled; +#else + return false; +#endif +} + +BHandler * +BPoseView::ResolveSpecifier(BMessage *_SCRIPTING_ONLY(message), + int32 _SCRIPTING_ONLY(index), BMessage *_SCRIPTING_ONLY(specifier), + int32 _SCRIPTING_ONLY(form), const char *_SCRIPTING_ONLY(property)) +{ +#if _SUPPORTS_FEATURE_SCRIPTING + BPropertyInfo propertyInfo(const_cast(kPosesPropertyList)); + + int32 result = propertyInfo.FindMatch(message, index, specifier, form, property); + if (result < 0) { +// PRINT(("FindMatch result %d \n")); + return _inherited::ResolveSpecifier(message, index, specifier, + form, property); + } + + return this; +#else + return NULL; +#endif +} + +BPose * +BPoseView::FindPose(const entry_ref *_SCRIPTING_ONLY(ref), + int32 _SCRIPTING_ONLY(specifierForm), int32 *_SCRIPTING_ONLY(index)) const +{ +#if _SUPPORTS_FEATURE_SCRIPTING + // flavor of FindPose, used by previous/next specifiers + + BPose *pose = FindPose(ref, index); + + if (specifierForm == (int32)kPreviousSpecifier) + return PoseAtIndex(--*index); + else if (specifierForm == (int32)kNextSpecifier) + return PoseAtIndex(++*index); + else + return pose; +#else + return NULL; +#endif +} + diff --git a/src/kits/tracker/PublicCommands.h b/src/kits/tracker/PublicCommands.h new file mode 100644 index 0000000000..bb18e0eafc --- /dev/null +++ b/src/kits/tracker/PublicCommands.h @@ -0,0 +1,59 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef __PUBLIC_COMMANDS__ +#define __PUBLIC_COMMANDS__ + +#include + +// commands that may be issued to the tracker by other apps using messengers +namespace BPrivate { + +const uint32 kFindButton = 'Tfnd'; +const uint32 kSaveButton = 'Tsav'; +const uint32 kShowSplash = 'Spls'; + +const uint32 kStartWatchClipboardRefs = 'TCbw'; + // StartWatching() clipboard changes. Changes will be sent to given BMessenger "target" +const uint32 kStopWatchClipboardRefs = 'TCfw'; + // StopWatching() given BMessenger "target" +const uint32 kFSClipboardChanges = 'TCch'; + // Used by FSClipboard functions which change refs in clipboard and are used outside Tracker (like BFilePanel called in another app) + // Contains movemodes named as in FSClipboard operations and in Clipboard (look into FSClipboard files) + +} // namespace BPrivate + +using namespace BPrivate; + +#endif /* __PUBLIC_COMMANDS__ */ diff --git a/src/kits/tracker/QueryContainerWindow.cpp b/src/kits/tracker/QueryContainerWindow.cpp new file mode 100644 index 0000000000..2dd104f48a --- /dev/null +++ b/src/kits/tracker/QueryContainerWindow.cpp @@ -0,0 +1,183 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include +#include +#include +#include +#include +#include + +#include "Attributes.h" +#include "Commands.h" +#include "QueryContainerWindow.h" +#include "QueryPoseView.h" + + +BQueryContainerWindow::BQueryContainerWindow(LockingList *windowList, + uint32 containerWindowFlags, window_look look, + window_feel feel, uint32 flags, uint32 workspace) + : BContainerWindow(windowList, containerWindowFlags, look, feel, + flags, workspace) +{ +} + + +BPoseView * +BQueryContainerWindow::NewPoseView(Model *model, BRect rect, uint32) +{ + return new BQueryPoseView(model, rect); +} + + +BQueryPoseView * +BQueryContainerWindow::PoseView() const +{ + return static_cast(fPoseView); +} + + +void +BQueryContainerWindow::CreatePoseView(Model *model) +{ + BRect rect(Bounds()); + rect.right -= B_V_SCROLL_BAR_WIDTH; + rect.bottom -= B_H_SCROLL_BAR_HEIGHT; + fPoseView = NewPoseView(model, rect, kListMode); + + AddChild(fPoseView); +} + + +void +BQueryContainerWindow::AddWindowMenu(BMenu *menu) +{ + BMenuItem *item; + + item = new BMenuItem("Resize to Fit", new BMessage(kResizeToFit), 'Y'); + item->SetTarget(this); + menu->AddItem(item); + + item = new BMenuItem("Select"B_UTF8_ELLIPSIS, new BMessage(kShowSelectionWindow), 'A', B_SHIFT_KEY); + item->SetTarget(PoseView()); + menu->AddItem(item); + + item = new BMenuItem("Select All", new BMessage(B_SELECT_ALL), 'A'); + item->SetTarget(PoseView()); + menu->AddItem(item); + + item = new BMenuItem("Invert Selection", new BMessage(kInvertSelection), 'S'); + item->SetTarget(PoseView()); + menu->AddItem(item); + + item = new BMenuItem("Close", new BMessage(B_QUIT_REQUESTED), 'W'); + item->SetTarget(this); + menu->AddItem(item); +} + + +void +BQueryContainerWindow::AddWindowContextMenus(BMenu *menu) +{ + BMenuItem *resizeItem = new BMenuItem("Resize to Fit", + new BMessage(kResizeToFit), 'Y'); + menu->AddItem(resizeItem); + menu->AddItem(new BMenuItem("Select"B_UTF8_ELLIPSIS, new BMessage(kShowSelectionWindow), 'A', B_SHIFT_KEY)); + menu->AddItem(new BMenuItem("Select All", new BMessage(B_SELECT_ALL), 'A')); + BMenuItem *closeItem = new BMenuItem("Close", + new BMessage(B_QUIT_REQUESTED), 'W'); + menu->AddItem(closeItem); + // target items as needed + menu->SetTargetForItems(PoseView()); + closeItem->SetTarget(this); + resizeItem->SetTarget(this); +} + + +void +BQueryContainerWindow::SetUpDefaultState() +{ + BNode defaultingNode; + + WindowStateNodeOpener opener(this, true); + // this is our destination node, whatever it is for this window + if (!opener.StreamNode()) + return; + + BString defaultStatePath(kQueryTemplates); + BString sanitizedType(PoseView()->SearchForType()); + + defaultStatePath += '/'; + int32 length = sanitizedType.Length(); + char *buf = sanitizedType.LockBuffer(length); + for (int32 index = length - 1; index >= 0; index--) + if (buf[index] == '/') + buf[index] = '_'; + sanitizedType.UnlockBuffer(length); + + defaultStatePath += sanitizedType; + + PRINT(("looking for default query state at %s\n", defaultStatePath.String())); + + if (!DefaultStateSourceNode(defaultStatePath.String(), &defaultingNode, false)) { + TRACE(); + return; + } + + // copy over the attributes + + // set up a filter of the attributes we want copied + const char *allowAttrs[] = { + kAttrWindowFrame, + kAttrViewState, + kAttrViewStateForeign, + kAttrColumns, + kAttrColumnsForeign, + 0 + }; + + // do it + AttributeStreamMemoryNode memoryNode; + NamesToAcceptAttrFilter filter(allowAttrs); + AttributeStreamFileNode fileNode(&defaultingNode); + *opener.StreamNode() << memoryNode << filter << fileNode; +} + + +bool +BQueryContainerWindow::ActiveOnDevice(dev_t device) const +{ + return PoseView()->ActiveOnDevice(device); +} + diff --git a/src/kits/tracker/QueryContainerWindow.h b/src/kits/tracker/QueryContainerWindow.h new file mode 100644 index 0000000000..228c1d1d09 --- /dev/null +++ b/src/kits/tracker/QueryContainerWindow.h @@ -0,0 +1,78 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef _QUERY_CONTAINER_WINDOW_H +#define _QUERY_CONTAINER_WINDOW_H + +#include "ContainerWindow.h" + +namespace BPrivate { + +// Container window specificaly used for displaying BQueryPoseViews +// Adds query window specific menus + + +#define kQueryTemplates "DefaultQueryTemplates" + +class BQueryPoseView; + +class BQueryContainerWindow : public BContainerWindow { +public: + BQueryContainerWindow(LockingList *windowList, + uint32 containerWindowFlags, + window_look look = B_DOCUMENT_WINDOW_LOOK, + window_feel feel = B_NORMAL_WINDOW_FEEL, + uint32 flags = B_WILL_ACCEPT_FIRST_CLICK | B_NO_WORKSPACE_ACTIVATION, + uint32 workspace = B_CURRENT_WORKSPACE); + + BQueryPoseView *PoseView() const; + bool ActiveOnDevice(dev_t) const; + +protected: + virtual void CreatePoseView(Model *); + virtual BPoseView *NewPoseView(Model *model, BRect rect, uint32 viewMode); + virtual void AddWindowMenu(BMenu *menu); + virtual void AddWindowContextMenus(BMenu *menu); + + virtual void SetUpDefaultState(); + +private: + typedef BContainerWindow _inherited; +}; + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/QueryPoseView.cpp b/src/kits/tracker/QueryPoseView.cpp new file mode 100644 index 0000000000..81ee0c491e --- /dev/null +++ b/src/kits/tracker/QueryPoseView.cpp @@ -0,0 +1,662 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include +#include +#include +#include +#include +#include + +#include "Attributes.h" +#include "AttributeStream.h" +#include "AutoLock.h" +#include "Commands.h" +#include "FindPanel.h" +#include "FSUtils.h" +#include "MimeTypeList.h" +#include "MimeTypes.h" +#include "QueryPoseView.h" +#include "Tracker.h" + +#include + +// Currently filtering out Trash doesn't node monitor too well - if you +// remove an item from the Trash, it doesn't show up in the query result +// To do this properly, we would have to node monitor everything BQuery +// returns and after a node monitor re-chech if it should be part of +// query results and add/remove appropriately. Right now only moving to +// Trash is supported + +BQueryPoseView::BQueryPoseView(Model *model, BRect frame, uint32 resizeMask) + : BPoseView(model, frame, kListMode, resizeMask), + fShowResultsFromTrash(false), + fQueryList(NULL), + fQueryListContainer(NULL), + fCreateOldPoseList(false) +{ +} + + +BQueryPoseView::~BQueryPoseView() +{ + delete fQueryListContainer; +} + + +void +BQueryPoseView::MessageReceived(BMessage *message) +{ + switch (message->what) { + case kFSClipboardChanges: + { + // poses have always to be updated for the query view + UpdatePosesClipboardModeFromClipboard(message); + break; + } + + default: + _inherited::MessageReceived(message); + break; + } +} + + +void +BQueryPoseView::EditQueries() +{ + BMessage message(kEditQuery); + message.AddRef("refs", TargetModel()->EntryRef()); + BMessenger(kTrackerSignature, -1, 0).SendMessage(&message); +} + + +void +BQueryPoseView::SetUpDefaultColumnsIfNeeded() +{ + // in case there were errors getting some columns + if (fColumnList->CountItems() != 0) + return; + + fColumnList->AddItem(new BColumn("Name", kColumnStart, 145, B_ALIGN_LEFT, + kAttrStatName, B_STRING_TYPE, true, true)); + fColumnList->AddItem(new BColumn("Path", 200, 225, B_ALIGN_LEFT, + kAttrPath, B_STRING_TYPE, true, false)); + fColumnList->AddItem(new BColumn("Size", 440, 80, B_ALIGN_RIGHT, + kAttrStatSize, B_OFF_T_TYPE, true, false)); + fColumnList->AddItem(new BColumn("Modified", 535, 150, B_ALIGN_LEFT, + kAttrStatModified, B_TIME_TYPE, true, false)); +} + + +void +BQueryPoseView::AttachedToWindow() +{ + _inherited::AttachedToWindow(); + SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR)); +} + + +void +BQueryPoseView::RestoreState(AttributeStreamNode *node) +{ + _inherited::RestoreState(node); + fViewState->SetViewMode(kListMode); +} + + +void +BQueryPoseView::RestoreState(const BMessage &message) +{ + _inherited::RestoreState(message); + fViewState->SetViewMode(kListMode); +} + + +void +BQueryPoseView::SavePoseLocations(BRect *) +{ +} + + +void +BQueryPoseView::SetViewMode(uint32) +{ +} + + +void +BQueryPoseView::OpenParent() +{ +} + + +void +BQueryPoseView::Refresh() +{ + PRINT(("refreshing dynamic date query\n")); + + // cause the old AddPosesTask to die + fAddPosesThreads.clear(); + delete fQueryListContainer; + fQueryListContainer = NULL; + + fCreateOldPoseList = true; + AddPoses(TargetModel()); + TargetModel()->CloseNode(); + + ResetOrigin(); + ResetPosePlacementHint(); +} + + +bool +BQueryPoseView::ShouldShowPose(const Model *model, const PoseInfo *poseInfo) +{ + // add_poses, etc. filter + ASSERT(TargetModel()); + + if (!fShowResultsFromTrash + && dynamic_cast(be_app)->InTrashNode(model->EntryRef())) + return false; + + bool result = _inherited::ShouldShowPose(model, poseInfo); + + PoseList *oldPoseList = fQueryListContainer->OldPoseList(); + if (result && oldPoseList) { + // pose will get added - remove it from the old pose list + // because it is supposed to be showing + BPose *pose = oldPoseList->FindPose(model); + if (pose) + oldPoseList->RemoveItem(pose); + } + return result; +} + + +void +BQueryPoseView::AddPosesCompleted() +{ + ASSERT(Window()->IsLocked()); + + PoseList *oldPoseList = fQueryListContainer->OldPoseList(); + if (oldPoseList) { + int32 count = oldPoseList->CountItems(); + for (int32 index = count - 1; index >= 0; index--) { + BPose *pose = oldPoseList->ItemAt(index); + DeletePose(pose->TargetModel()->NodeRef()); + } + fQueryListContainer->ClearOldPoseList(); + } + + _inherited::AddPosesCompleted(); +} + + +// When using dynamic dates, such as "today", need to refresh the query +// window every now and then + +EntryListBase * +BQueryPoseView::InitDirentIterator(const entry_ref *ref) +{ + BEntry entry(ref); + if (entry.InitCheck() != B_OK) + return NULL; + + Model sourceModel(&entry, true); + if (sourceModel.InitCheck() != B_OK) + return NULL; + + ASSERT(sourceModel.IsQuery()); + + // old pose list is used for finding poses that no longer match a + // dynamic date query during a Refresh call + PoseList *oldPoseList = NULL; + if (fCreateOldPoseList) { + oldPoseList = new PoseList(10, false); + oldPoseList->AddList(fPoseList); + } + + fQueryListContainer = new QueryEntryListCollection(&sourceModel, this, oldPoseList); + fCreateOldPoseList = false; + + if (fQueryListContainer->InitCheck() != B_OK) { + delete fQueryListContainer; + fQueryListContainer = NULL; + return NULL; + } + + fShowResultsFromTrash = fQueryListContainer->ShowResultsFromTrash(); + + TTracker::WatchNode(sourceModel.NodeRef(), B_WATCH_NAME | B_WATCH_STAT + | B_WATCH_ATTR, this); + + fQueryList = fQueryListContainer->QueryList(); + + if (fQueryListContainer->DynamicDateQuery()) { + + // calculate the time to trigger the query refresh - next midnight + time_t now = time(0); + + time_t nextMidnight = now + 60 * 60 * 24; // move ahead by a day + tm timeData; + localtime_r(&nextMidnight, &timeData); + timeData.tm_sec = 0; + timeData.tm_min = 0; + timeData.tm_hour = 0; + nextMidnight = mktime(&timeData); + + time_t nextHour = now + 60 * 60; // move ahead by a hour + localtime_r(&nextHour, &timeData); + timeData.tm_sec = 0; + timeData.tm_min = 0; + nextHour = mktime(&timeData); + + PRINT(("%ld minutes, %ld seconds till next hour\n", (nextHour - now) / 60, + (nextHour - now) % 60)); + + time_t nextMinute = now + 60; // move ahead by a minute + localtime_r(&nextMinute, &timeData); + timeData.tm_sec = 0; + nextMinute = mktime(&timeData); + + PRINT(("%ld seconds till next minute\n", nextMinute - now)); + + bigtime_t delta; + if (fQueryListContainer->DynamicDateRefreshEveryMinute()) + delta = nextMinute - now; + else if (fQueryListContainer->DynamicDateRefreshEveryHour()) + delta = nextHour - now; + else + delta = nextMidnight - now; + +#if DEBUG + int32 secondsTillMidnight = (nextMidnight - now); + int32 minutesTillMidnight = secondsTillMidnight/60; + secondsTillMidnight %= 60; + int32 hoursTillMidnight = minutesTillMidnight/60; + minutesTillMidnight %= 60; + + PRINT(("%ld hours, %ld minutes, %ld seconds till midnight\n", + hoursTillMidnight, minutesTillMidnight, secondsTillMidnight)); + + int32 refreshInSeconds = delta % 60; + int32 refreshInMinutes = delta / 60; + int32 refreshInHours = refreshInMinutes / 60; + refreshInMinutes %= 60; + + PRINT(("next refresh in %ld hours, %ld minutes, %ld seconds\n", + refreshInHours, refreshInMinutes, refreshInSeconds)); +#endif + + // bump up to microseconds + delta *= 1000000; + + TTracker *tracker = dynamic_cast(be_app); + ASSERT(tracker); + tracker->MainTaskLoop()->RunLater( + NewLockingMemberFunctionObject(&BQueryPoseView::Refresh, this), delta); + } + + return fQueryListContainer->Clone(); +} + + +uint32 +BQueryPoseView::WatchNewNodeMask() +{ + return B_WATCH_NAME | B_WATCH_STAT | B_WATCH_ATTR; +} + + +const char * +BQueryPoseView::SearchForType() const +{ + if (!fSearchForMimeType.Length()) { + BModelOpener opener(TargetModel()); + BString buffer; + attr_info attrInfo; + // read the type of files we are looking for + status_t status = TargetModel()->Node()->GetAttrInfo(kAttrQueryInitialMime, &attrInfo); + if (status == B_OK) + TargetModel()->Node()->ReadAttrString(kAttrQueryInitialMime, &buffer); + + if (buffer.Length()) { + TTracker *tracker = dynamic_cast(be_app); + if (tracker) { + const ShortMimeInfo *info = tracker->MimeTypes()->FindMimeType(buffer.String()); + if (info) + fSearchForMimeType = info->InternalName(); + + } + } + if (!fSearchForMimeType.Length()) + fSearchForMimeType = B_FILE_MIMETYPE; + } + + return fSearchForMimeType.String(); +} + + +bool +BQueryPoseView::ActiveOnDevice(dev_t device) const +{ + int32 count = fQueryList->CountItems(); + for (int32 index = 0; index < count; index++) + if (fQueryList->ItemAt(index)->TargetDevice() == device) + return true; + + return false; +} + + +// #pragma mark - + + +QueryEntryListCollection::QueryEntryListCollection(Model *model, BHandler *target, + PoseList *oldPoseList) + : fQueryListRep(new QueryListRep(new BObjectList(5, true))) +{ + Rewind(); + attr_info info; + BQuery query; + + if (!model->Node()) { + fStatus = B_ERROR; + return; + } + + // read the actual query string + fStatus = model->Node()->GetAttrInfo(kAttrQueryString, &info); + if (fStatus != B_OK) + return; + + BString buffer; + if (model->Node()->ReadAttr(kAttrQueryString, B_STRING_TYPE, 0, + buffer.LockBuffer((int32)info.size), (size_t)info.size) != info.size) { + fStatus = B_ERROR; + return; + } + + buffer.UnlockBuffer(); + + // read the extra options + MoreOptionsStruct saveMoreOptions; + if (ReadAttr(model->Node(), kAttrQueryMoreOptions, kAttrQueryMoreOptionsForeign, + B_RAW_TYPE, 0, &saveMoreOptions, sizeof(MoreOptionsStruct), + &MoreOptionsStruct::EndianSwap) != kReadAttrFailed) + fQueryListRep->fShowResultsFromTrash = saveMoreOptions.searchTrash; + + fStatus = query.SetPredicate(buffer.String()); + + fQueryListRep->fOldPoseList = oldPoseList; + fQueryListRep->fDynamicDateQuery = false; + + fQueryListRep->fRefreshEveryHour = false; + fQueryListRep->fRefreshEveryMinute = false; + + if (model->Node()->ReadAttr(kAttrDynamicDateQuery, B_BOOL_TYPE, 0, + &fQueryListRep->fDynamicDateQuery, sizeof(bool)) != sizeof(bool)) + fQueryListRep->fDynamicDateQuery = false; + + if (fQueryListRep->fDynamicDateQuery) { + // only refresh every minute on debug builds + fQueryListRep->fRefreshEveryMinute = buffer.IFindFirst("second") != -1 + || buffer.IFindFirst("minute") != -1; + fQueryListRep->fRefreshEveryHour = fQueryListRep->fRefreshEveryMinute + || buffer.IFindFirst("hour") != -1; + +#if !DEBUG + // don't refresh every minute unless we are running debug build + fQueryListRep->fRefreshEveryMinute = false; +#endif + } + + if (fStatus != B_OK) + return; + + bool searchAllVolumes = true; + status_t result = B_OK; + + // get volumes to perform query on + if (model->Node()->GetAttrInfo(kAttrQueryVolume, &info) == B_OK) { + char *buffer = NULL; + + if ((buffer = (char *)malloc(info.size)) != NULL + && model->Node()->ReadAttr(kAttrQueryVolume, B_MESSAGE_TYPE, 0, buffer, + (size_t)info.size) == info.size) { + + BMessage message; + if (message.Unflatten(buffer) == B_OK) { + for (int32 index = 0; ;index++) { + ASSERT(index < 100); + BVolume volume; + // match a volume with the info embedded in the message + result = MatchArchivedVolume(&volume, &message, index); + if (result == B_OK) { + // start the query on this volume + result = FetchOneQuery(&query, target, + fQueryListRep->fQueryList, &volume); + if (result != B_OK) + continue; + + searchAllVolumes = false; + } else if (result != B_DEV_BAD_DRIVE_NUM) + // if B_DEV_BAD_DRIVE_NUM, the volume just isn't mounted this + // time around, keep looking for more + // if other error, bail + break; + } + } + } + + free(buffer); + } + + if (searchAllVolumes) { + // no specific volumes embedded in query, search everything + BVolumeRoster roster; + BVolume volume; + + roster.Rewind(); + while (roster.GetNextVolume(&volume) == B_OK) + if (volume.IsPersistent() && volume.KnowsQuery()) { + result = FetchOneQuery(&query, target, fQueryListRep->fQueryList, &volume); + if (result != B_OK) + continue; + } + } + + fStatus = B_OK; + return; +} + + +status_t +QueryEntryListCollection::FetchOneQuery(const BQuery *copyThis, + BHandler *target, BObjectList *list, BVolume *volume) +{ + BQuery *query = new BQuery; + // have to fake a copy constructor here because BQuery doesn't have + // a copy constructor + + BString buffer; + const_cast(copyThis)->GetPredicate(&buffer); + query->SetPredicate(buffer.String()); + + query->SetTarget(BMessenger(target)); + query->SetVolume(volume); + + status_t result = query->Fetch(); + if (result != B_OK) { + PRINT(("fetch error %s\n", strerror(result))); + return result; + } + list->AddItem(query); + + return B_OK; +} + + +QueryEntryListCollection::~QueryEntryListCollection() +{ + if (fQueryListRep->CloseQueryList()) + delete fQueryListRep; +} + + +QueryEntryListCollection * +QueryEntryListCollection::Clone() +{ + fQueryListRep->OpenQueryList(); + return new QueryEntryListCollection(*this); +} + + +QueryEntryListCollection::QueryEntryListCollection( + const QueryEntryListCollection &cloneThis) + : EntryListBase(), + fQueryListRep(cloneThis.fQueryListRep) +{ + // only to be used by the Clone routine +} + + +void +QueryEntryListCollection::ClearOldPoseList() +{ + delete fQueryListRep->fOldPoseList; + fQueryListRep->fOldPoseList = NULL; +} + + +status_t +QueryEntryListCollection::GetNextEntry(BEntry *entry, bool traverse) +{ + status_t result = B_ERROR; + + for (int32 count = fQueryListRep->fQueryList->CountItems(); + fQueryListRep->fQueryListIndex < count; + fQueryListRep->fQueryListIndex++) { + result = fQueryListRep->fQueryList->ItemAt(fQueryListRep->fQueryListIndex) + ->GetNextEntry(entry, traverse); + if (result == B_OK) + break; + } + return result; +} + + +int32 +QueryEntryListCollection::GetNextDirents(struct dirent *buffer, size_t length, + int32 count) +{ + int32 result = 0; + + for (int32 queryCount = fQueryListRep->fQueryList->CountItems(); + fQueryListRep->fQueryListIndex < queryCount; + fQueryListRep->fQueryListIndex++) { + + result = fQueryListRep->fQueryList->ItemAt(fQueryListRep->fQueryListIndex) + ->GetNextDirents(buffer, length, count); + if (result > 0) + break; + } + return result; +} + + +status_t +QueryEntryListCollection::GetNextRef(entry_ref *ref) +{ + status_t result = B_ERROR; + + for (int32 count = fQueryListRep->fQueryList->CountItems(); + fQueryListRep->fQueryListIndex < count; + fQueryListRep->fQueryListIndex++) { + + result = fQueryListRep->fQueryList->ItemAt(fQueryListRep->fQueryListIndex) + ->GetNextRef(ref); + if (result == B_OK) + break; + } + + return result; +} + + +status_t +QueryEntryListCollection::Rewind() +{ + fQueryListRep->fQueryListIndex = 0; + + return B_OK; +} + + +int32 +QueryEntryListCollection::CountEntries() +{ + return 0; +} + + +bool +QueryEntryListCollection::ShowResultsFromTrash() const +{ + return fQueryListRep->fShowResultsFromTrash; +} + + +bool +QueryEntryListCollection::DynamicDateQuery() const +{ + return fQueryListRep->fDynamicDateQuery; +} + + +bool +QueryEntryListCollection::DynamicDateRefreshEveryHour() const +{ + return fQueryListRep->fRefreshEveryHour; +} + + +bool +QueryEntryListCollection::DynamicDateRefreshEveryMinute() const +{ + return fQueryListRep->fRefreshEveryMinute; +} + diff --git a/src/kits/tracker/QueryPoseView.h b/src/kits/tracker/QueryPoseView.h new file mode 100644 index 0000000000..c1473d94d1 --- /dev/null +++ b/src/kits/tracker/QueryPoseView.h @@ -0,0 +1,183 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef _QUERY_POSE_VIEW_H +#define _QUERY_POSE_VIEW_H + +class BQuery; + +#include "EntryIterator.h" +#include "PoseView.h" + +namespace BPrivate { + +class BQueryContainerWindow; +class QueryEntryListCollection; + +class BQueryPoseView : public BPoseView { +public: + BQueryPoseView(Model *, BRect, uint32 resizeMask = B_FOLLOW_ALL); + virtual ~BQueryPoseView(); + + virtual void MessageReceived(BMessage *message); + + const char *SearchForType() const; + BQueryContainerWindow *ContainerWindow() const; + bool ActiveOnDevice(dev_t) const; + + void Refresh(); + // makes queries that are static but need to appear dynamic + // live - for instance for a query that contains a + // date == today - RestartQuery gets called on midnight to update + // the contents + +protected: + virtual void AttachedToWindow(); + virtual void RestoreState(AttributeStreamNode *); + virtual void RestoreState(const BMessage &); + virtual void SavePoseLocations(BRect * = NULL); + virtual void SetUpDefaultColumnsIfNeeded(); + virtual void SetViewMode(uint32); + virtual void OpenParent(); + virtual void EditQueries(); + virtual EntryListBase *InitDirentIterator(const entry_ref *); + virtual uint32 WatchNewNodeMask(); + virtual bool ShouldShowPose(const Model *, const PoseInfo *); + virtual void AddPosesCompleted(); + +private: + // list of all the queries this PoseView represents + // typically there will be one query per volume specified + // QueryEntryListCollection provides the abstraction layer + // defining the iterators for _add_poses_ + bool fShowResultsFromTrash; + mutable BString fSearchForMimeType; + + BObjectList *fQueryList; + QueryEntryListCollection *fQueryListContainer; + + bool fCreateOldPoseList; + + typedef BPoseView _inherited; +}; + + +class QueryEntryListCollection : public EntryListBase { + // This will become a replacement for BDirectory and QueryList in a + // PoseView, allowing PoseView to have an arbitrary collection of + // elements that behave as an EntryList + // For now just manage a list of BQueries + + + class QueryListRep { + public: + QueryListRep(BObjectList *queryList) + : fQueryList(queryList), + fRefCount(0), + fShowResultsFromTrash(0), + fOldPoseList(NULL) + {} + + ~QueryListRep() + { + ASSERT(fRefCount <= 0); + delete fQueryList; + delete fOldPoseList; + } + + BObjectList *OpenQueryList() + { + fRefCount++; + return fQueryList; + } + + bool CloseQueryList() + { + return atomic_add(&fRefCount, -1) == 0; + } + + BObjectList *fQueryList; + int32 fRefCount; + bool fShowResultsFromTrash; + int32 fQueryListIndex; + bool fDynamicDateQuery; + bool fRefreshEveryHour; + bool fRefreshEveryMinute; + + PoseList *fOldPoseList; + // when doing a Refresh, this list is used to detect poses that + // are no longer a part of a fDynamicDateQuery and need to be removed + }; + +public: + + QueryEntryListCollection(Model *, BHandler * = NULL, PoseList *oldPoseList = NULL); + virtual ~QueryEntryListCollection(); + + QueryEntryListCollection *Clone(); + + BObjectList *QueryList() const + { return fQueryListRep->fQueryList; } + + PoseList *OldPoseList() const + { return fQueryListRep->fOldPoseList; } + void ClearOldPoseList(); + + virtual status_t GetNextEntry(BEntry *entry, bool traverse = false); + virtual status_t GetNextRef(entry_ref *ref); + virtual int32 GetNextDirents(struct dirent *buffer, size_t length, + int32 count = INT_MAX); + + virtual status_t Rewind(); + virtual int32 CountEntries(); + + bool ShowResultsFromTrash() const; + bool DynamicDateQuery() const; + bool DynamicDateRefreshEveryHour() const; + bool DynamicDateRefreshEveryMinute() const; + +private: + QueryEntryListCollection(const QueryEntryListCollection &); + // only to be used by the Clone routine + status_t FetchOneQuery(const BQuery *, BHandler *target, + BObjectList *, BVolume *); + + QueryListRep *fQueryListRep; +}; + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/RecentItems.cpp b/src/kits/tracker/RecentItems.cpp new file mode 100644 index 0000000000..03d6efac4f --- /dev/null +++ b/src/kits/tracker/RecentItems.cpp @@ -0,0 +1,457 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include + +#include "Attributes.h" +#include "IconMenuItem.h" +#include "Model.h" +#include "NavMenu.h" +#include "PoseView.h" +#include "RecentItems.h" +#include "SlowMenu.h" +#include "Tracker.h" +#include "Utilities.h" + +class RecentItemsMenu : public BSlowMenu { +public: + RecentItemsMenu(const char *title, BMessage *openMessage, + BHandler *itemTarget, int32 maxItems) + : BSlowMenu(title), + fTargetMesage(openMessage), + fItemTarget(itemTarget), + fMaxCount(maxItems) + {} + virtual ~RecentItemsMenu(); + + virtual bool StartBuildingItemList(); + virtual bool AddNextItem(); + virtual void DoneBuildingItemList() {} + virtual void ClearMenuBuildingState(); + +protected: + virtual const BMessage *FileMessage() + { return fTargetMesage; } + virtual const BMessage *ContainerMessage() + { return fTargetMesage; } + + BRecentItemsList *fTterator; + BMessage *fTargetMesage; + BHandler *fItemTarget; + int32 fCount; + int32 fSanityCount; + int32 fMaxCount; +}; + + +RecentItemsMenu::~RecentItemsMenu() +{ + delete fTterator; + delete fTargetMesage; +} + +bool +RecentItemsMenu::AddNextItem() +{ + BMenuItem *item = fTterator->GetNextMenuItem(FileMessage(), + ContainerMessage(), fItemTarget); + + if (item) { + AddItem(item); + fCount++; + } + fSanityCount++; + + return fCount < fMaxCount - 1 && (fSanityCount < fMaxCount + 20); + // fSanityCount is a hacky way of dealing with a lot of stale + // recent apps +} +bool +RecentItemsMenu::StartBuildingItemList() +{ + // remove any preexisting items + int32 itemCount = CountItems(); + while (itemCount--) + delete RemoveItem((int32)0); + + fCount = 0; + fSanityCount = 0; + fTterator->Rewind(); + return true; +} + + +void +RecentItemsMenu::ClearMenuBuildingState() +{ + fMenuBuilt = false; + // force rebuilding each time + fTterator->Rewind(); +} + + +BRecentItemsList::BRecentItemsList(int32 maxItems, bool navMenuFolders) + : fMaxItems(maxItems), + fNavMenuFolders(navMenuFolders) +{ + InitIconPreloader(); + // need the icon cache + Rewind(); +} + + +void +BRecentItemsList::Rewind() +{ + fIndex = 0; + fItems.MakeEmpty(); +} + + +BMenuItem * +BRecentItemsList::GetNextMenuItem(const BMessage *fileOpenInvokeMessage, + const BMessage *containerOpenInvokeMessage, + BHandler *target, entry_ref *currentItemRef) +{ + entry_ref ref; + if (GetNextRef(&ref) != B_OK) + return NULL; + + Model model(&ref, true); + if (model.InitCheck() != B_OK) + return NULL; + + bool container = false; + if (model.IsSymLink()) { + + Model *newResolvedModel = NULL; + Model *result = model.LinkTo(); + + if (!result) { + newResolvedModel = new Model(model.EntryRef(), true, true); + + if (newResolvedModel->InitCheck() != B_OK) { + // broken link, still can show though, bail + delete newResolvedModel; + result = NULL; + } else + result = newResolvedModel; + } + + if (result) { + BModelOpener opener(result); + // open the model, if it ain't open already + + PoseInfo poseInfo; + ssize_t size = -1; + + if (result->Node()) + size = result->Node()->ReadAttr(kAttrPoseInfo, B_RAW_TYPE, 0, + &poseInfo, sizeof(poseInfo)); + + result->CloseNode(); + + if (size == sizeof(poseInfo) && !BPoseView::PoseVisible(result, + &poseInfo, false)) { + // link target sez it doesn't want to be visible, + // don't show the link + PRINT(("not showing hidden item %s\n", model.Name())); + delete newResolvedModel; + return NULL; + } + ref = *result->EntryRef(); + container = result->IsContainer(); + } + model.SetLinkTo(result); + } else { + ref = *model.EntryRef(); + container = model.IsContainer(); + } + + // if user asked for it, return the current item ref + if (currentItemRef) + *currentItemRef = ref; + + BMessage *message; + if (container && containerOpenInvokeMessage) + message = new BMessage(*containerOpenInvokeMessage); + else if (!container && fileOpenInvokeMessage) + message = new BMessage(*fileOpenInvokeMessage); + else + message = new BMessage(B_REFS_RECEIVED); + + message->AddRef("refs", model.EntryRef()); + + // Truncate the name if necessary + BString truncatedString(model.Name()); + be_plain_font->TruncateString(&truncatedString, B_TRUNCATE_END, + BNavMenu::GetMaxMenuWidth()); + + ModelMenuItem *item = NULL; + if (!container || !fNavMenuFolders) + item = new ModelMenuItem(&model, truncatedString.String(), message); + else { + // add another nav menu item if it's a directory + BNavMenu *menu = new BNavMenu(truncatedString.String(), message->what, + target, 0); + + menu->SetNavDir(&ref); + ModelMenuItem *item = new ModelMenuItem(&model, menu); + item->SetMessage(message); + } + + if (item && target) + item->SetTarget(target); + + return item; +} + +status_t +BRecentItemsList::GetNextRef(entry_ref *result) +{ + return fItems.FindRef("refs", fIndex++, result); +} + +// #pragma mark - + +BRecentFilesList::BRecentFilesList(int32 maxItems, bool navMenuFolders, + const char *ofType, const char *openedByAppSig) + : BRecentItemsList(maxItems, navMenuFolders), + fType(ofType), + fTypes(NULL), + fTypeCount(0), + fAppSig(openedByAppSig) +{ +} + + +BRecentFilesList::BRecentFilesList(int32 maxItems, bool navMenuFolders, + const char *ofTypeList[], int32 ofTypeListCount, const char *openedByAppSig) + : BRecentItemsList(maxItems, navMenuFolders), + fType(NULL), + fTypes(NULL), + fTypeCount(ofTypeListCount), + fAppSig(openedByAppSig) +{ + if (fTypeCount) { + fTypes = new char *[ofTypeListCount]; + for (int32 index = 0; index < ofTypeListCount; index++) + fTypes[index] = strdup(ofTypeList[index]); + } +} + + +BRecentFilesList::~BRecentFilesList() +{ + if (fTypeCount) { + for (int32 index = 0; index < fTypeCount; index++) + free(fTypes[index]); + delete [] fTypes; + } +} + +status_t +BRecentFilesList::GetNextRef(entry_ref *ref) +{ + if (fIndex == 0) { + // Lazy roster Get + if (fTypes) + BRoster().GetRecentDocuments(&fItems, fMaxItems, + const_cast(fTypes), + fTypeCount, fAppSig.Length() ? fAppSig.String() : NULL); + else + BRoster().GetRecentDocuments(&fItems, fMaxItems, + fType.Length() ? fType.String() : NULL, + fAppSig.Length() ? fAppSig.String() : NULL); + + } + return BRecentItemsList::GetNextRef(ref); +} + + +class RecentFilesMenu : public RecentItemsMenu { +public: + RecentFilesMenu(const char *title, BMessage *openFileMessage, + BMessage *openFolderMessage, BHandler *target, + int32 maxItems, bool navMenuFolders, const char *ofType, + const char *openedByAppSig); + + RecentFilesMenu(const char *title, BMessage *openFileMessage, + BMessage *openFolderMessage, BHandler *target, + int32 maxItems, bool navMenuFolders, const char *ofTypeList[], + int32 ofTypeListCount, const char *openedByAppSig); + + virtual ~RecentFilesMenu(); + +protected: + virtual const BMessage *ContainerMessage() + { return openFolderMessage; } + +private: + BMessage *openFolderMessage; +}; + + +RecentFilesMenu::RecentFilesMenu(const char *title, BMessage *openFileMessage, + BMessage *openFolderMessage, BHandler *target, int32 maxItems, + bool navMenuFolders, const char *ofType, const char *openedByAppSig) + : RecentItemsMenu(title, openFileMessage, target, maxItems), + openFolderMessage(openFolderMessage) +{ + fTterator = new BRecentFilesList(maxItems + 10, navMenuFolders, + ofType, openedByAppSig); +} + + +RecentFilesMenu::RecentFilesMenu(const char *title, BMessage *openFileMessage, + BMessage *openFolderMessage, BHandler *target, int32 maxItems, + bool navMenuFolders, const char *ofTypeList[], int32 ofTypeListCount, + const char *openedByAppSig) + : RecentItemsMenu(title, openFileMessage, target, maxItems), + openFolderMessage(openFolderMessage) +{ + fTterator = new BRecentFilesList(maxItems + 10, navMenuFolders, + ofTypeList, ofTypeListCount, openedByAppSig); +} + +RecentFilesMenu::~RecentFilesMenu() +{ + delete openFolderMessage; +} + +BMenu * +BRecentFilesList::NewFileListMenu(const char *title, + BMessage *openFileMessage, BMessage *openFolderMessage, + BHandler *target, int32 maxItems, bool navMenuFolders, const char *ofType, + const char *openedByAppSig) +{ + return new RecentFilesMenu(title, openFileMessage, + openFolderMessage, target, maxItems, navMenuFolders, ofType, openedByAppSig); +} + +BMenu * +BRecentFilesList::NewFileListMenu(const char *title, + BMessage *openFileMessage, BMessage *openFolderMessage, + BHandler *target, int32 maxItems, bool navMenuFolders, const char *ofTypeList[], + int32 ofTypeListCount, const char *openedByAppSig) +{ + return new RecentFilesMenu(title, openFileMessage, + openFolderMessage, target, maxItems, navMenuFolders, ofTypeList, + ofTypeListCount, openedByAppSig); +} + +// #pragma mark - + +class RecentFoldersMenu : public RecentItemsMenu { +public: + RecentFoldersMenu(const char *title, BMessage *openMessage, + BHandler *target, int32 maxItems, bool navMenuFolders, + const char *openedByAppSig); +}; + +RecentFoldersMenu::RecentFoldersMenu(const char *title, BMessage *openMessage, + BHandler *target, int32 maxItems, bool navMenuFolders, const char *openedByAppSig) + : RecentItemsMenu(title, openMessage, target, maxItems) +{ + fTterator = new BRecentFoldersList(maxItems + 10, navMenuFolders, + openedByAppSig); +} + +BMenu * +BRecentFoldersList::NewFolderListMenu(const char *title, + BMessage *openMessage, BHandler *target, int32 maxItems, + bool navMenuFolders, const char *openedByAppSig) +{ + return new RecentFoldersMenu(title, openMessage, target, maxItems, + navMenuFolders, openedByAppSig); +} + +BRecentFoldersList::BRecentFoldersList(int32 maxItems, bool navMenuFolders, + const char *openedByAppSig) + : BRecentItemsList(maxItems, navMenuFolders), + fAppSig(openedByAppSig) +{ +} + +status_t +BRecentFoldersList::GetNextRef(entry_ref *ref) +{ + if (fIndex == 0) { + // Lazy roster Get + BRoster().GetRecentFolders(&fItems, fMaxItems, + fAppSig.Length() ? fAppSig.String() : NULL); + + } + return BRecentItemsList::GetNextRef(ref); +} + +// #pragma mark - + +BRecentAppsList::BRecentAppsList(int32 maxItems) + : BRecentItemsList(maxItems, false) +{ +} + +status_t +BRecentAppsList::GetNextRef(entry_ref *ref) +{ + if (fIndex == 0) { + // Lazy roster Get + BRoster().GetRecentApps(&fItems, fMaxItems); + } + return BRecentItemsList::GetNextRef(ref); +} + +class RecentAppsMenu : public RecentItemsMenu { +public: + RecentAppsMenu(const char *title, BMessage *openMessage, + BHandler *target, int32 maxItems); +}; + + +RecentAppsMenu::RecentAppsMenu(const char *title, BMessage *openMessage, + BHandler *target, int32 maxItems) + : RecentItemsMenu(title, openMessage, target, maxItems) +{ + fTterator = new BRecentAppsList(maxItems); +} + + +BMenu * +BRecentAppsList::NewAppListMenu(const char *title, BMessage *openMessage, + BHandler *target, int32 maxItems) +{ + return new RecentAppsMenu(title, openMessage, target, maxItems); +} + diff --git a/src/kits/tracker/RecentItems.h b/src/kits/tracker/RecentItems.h new file mode 100644 index 0000000000..8a94d942b0 --- /dev/null +++ b/src/kits/tracker/RecentItems.h @@ -0,0 +1,207 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef __RECENT_ITEMS_LIST__ +#define __RECENT_ITEMS_LIST__ + +#include +#include +#include + +/* BRecentItemsList classes allow creating an entire menu with + * recent files, folders, apps. If the user wishes to add items to + * their own menu, they can instead use the GetNextMenuItem call to + * get one menu at a time to add it to their app. + */ + +class BMenuItem; +class BMenu; + +class BRecentItemsList { +public: + BRecentItemsList(int32 maxItems, bool navMenuFolders); + /* if passed, folder items get NavMenu-style + * subdirectories attached to them + */ + + virtual ~BRecentItemsList() {} + + virtual void Rewind(); + /* resets the iteration */ + + virtual BMenuItem *GetNextMenuItem(const BMessage *fileOpenMessage = NULL, + const BMessage *containerOpenMessage = NULL, + BHandler *target = NULL, entry_ref *currentItemRef = NULL); + /* if specified, the item for a file gets a copy with + * the item ref attached as "refs", otherwise a default B_REFS_RECEIVED + * message message gets attached + * if specified, the item for a folder, volume or query + * gets a copy with the item ref attached as "refs", otherwise a default + * B_REFS_RECEIVED message message gets attached + * if gets passed, the caller gets to look at the + * entry_ref corresponding to the item + */ + + virtual status_t GetNextRef(entry_ref *); + +protected: + BMessage fItems; + int32 fIndex; + int32 fMaxItems; + bool fNavMenuFolders; + +private: + + virtual void _r1(); + virtual void _r2(); + virtual void _r3(); + virtual void _r4(); + virtual void _r5(); + virtual void _r6(); + virtual void _r7(); + virtual void _r8(); + virtual void _r9(); + virtual void _r10(); + + uint32 _reserved[20]; +}; + +class BRecentFilesList : public BRecentItemsList { +public: + + /* use one of the two constructors to set up next item iteration */ + BRecentFilesList(int32 maxItems = 10, bool navMenuFolders = false, + const char *ofType = NULL, const char *openedByAppSig = NULL); + BRecentFilesList(int32 maxItems, bool navMenuFolders, const char *ofTypeList[], + int32 ofTypeListCount, const char *openedByAppSig = NULL); + virtual ~BRecentFilesList(); + + /* use one of the two NewFileListMenu calls to get an entire menu */ + static BMenu *NewFileListMenu(const char *title, + BMessage *openFileMessage = NULL, BMessage *openFolderMessage = NULL, + BHandler *target = NULL, + int32 maxItems = 10, bool navMenuFolders = false, + const char *ofType = NULL, const char *openedByAppSig = NULL); + + static BMenu *NewFileListMenu(const char *title, + BMessage *openFileMessage, BMessage *openFolderMessage, + BHandler *target, + int32 maxItems, bool navMenuFolders, + const char *ofTypeList[], int32 ofTypeListCount, + const char *openedByAppSig); + + virtual status_t GetNextRef(entry_ref *); + +protected: + + BString fType; + char **fTypes; + int32 fTypeCount; + BString fAppSig; + +private: + virtual void _r11(); + virtual void _r12(); + virtual void _r13(); + virtual void _r14(); + virtual void _r15(); + virtual void _r16(); + virtual void _r17(); + virtual void _r18(); + virtual void _r19(); + virtual void _r110(); + + uint32 _reserved[20]; +}; + +class BRecentFoldersList : public BRecentItemsList { +public: + /* use the constructor to set up next item iteration */ + BRecentFoldersList(int32 maxItems, bool navMenuFolders = false, + const char *openedByAppSig = NULL); + + /* use NewFolderListMenu to get an entire menu */ + static BMenu *NewFolderListMenu(const char *title, + BMessage *openMessage = NULL, BHandler *target = NULL, + int32 maxItems = 10, bool navMenuFolders = false, + const char *openedByAppSig = NULL); + + virtual status_t GetNextRef(entry_ref *); + +protected: + BString fAppSig; + +private: + virtual void _r21(); + virtual void _r22(); + virtual void _r23(); + virtual void _r24(); + virtual void _r25(); + virtual void _r26(); + virtual void _r27(); + virtual void _r28(); + virtual void _r29(); + virtual void _r210(); + + uint32 _reserved[20]; +}; + +class BRecentAppsList : public BRecentItemsList { +public: + /* use the constructor to set up next item iteration */ + BRecentAppsList(int32 maxItems); + + /* use NewFolderListMenu to get an entire menu */ + static BMenu *NewAppListMenu(const char *title, + BMessage *openMessage = NULL, BHandler *target = NULL, + int32 maxItems = 10); + + virtual status_t GetNextRef(entry_ref *); + +private: + virtual void _r31(); + virtual void _r32(); + virtual void _r33(); + virtual void _r34(); + virtual void _r35(); + virtual void _r36(); + virtual void _r37(); + virtual void _r38(); + virtual void _r39(); + virtual void _r310(); + + uint32 _reserved[20]; +}; + +#endif diff --git a/src/kits/tracker/RegExp.cpp b/src/kits/tracker/RegExp.cpp new file mode 100644 index 0000000000..f03fcc8c38 --- /dev/null +++ b/src/kits/tracker/RegExp.cpp @@ -0,0 +1,1340 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// This code is based on regexp.c, v.1.3 by Henry Spencer: + +// @(#)regexp.c 1.3 of 18 April 87 +// +// Copyright (c) 1986 by University of Toronto. +// Written by Henry Spencer. Not derived from licensed software. +// +// Permission is granted to anyone to use this software for any +// purpose on any computer system, and to redistribute it freely, +// subject to the following restrictions: +// +// 1. The author is not responsible for the consequences of use of +// this software, no matter how awful, even if they arise +// from defects in it. +// +// 2. The origin of this software must not be misrepresented, either +// by explicit claim or by omission. +// +// 3. Altered versions must be plainly marked as such, and must not +// be misrepresented as being the original software. +// +// Beware that some of this code is subtly aware of the way operator +// precedence is structured in regular expressions. Serious changes in +// regular-expression syntax might require a total rethink. +// + +// ALTERED VERSION: Adapted to ANSI C and C++ for the OpenTracker +// project (www.opentracker.org), Jul 11, 2000. + +#include +#include +#include + +#include + +#include "RegExp.h" + +// The first byte of the regexp internal "program" is actually this magic +// number; the start node begins in the second byte. + +const uint8 kRegExpMagic = 0234; + +// The "internal use only" fields in RegExp.h are present to pass info from +// compile to execute that permits the execute phase to run lots faster on +// simple cases. They are: +// +// regstart char that must begin a match; '\0' if none obvious +// reganch is the match anchored (at beginning-of-line only)? +// regmust string (pointer into program) that match must include, or NULL +// regmlen length of regmust string +// +// Regstart and reganch permit very fast decisions on suitable starting points +// for a match, cutting down the work a lot. Regmust permits fast rejection +// of lines that cannot possibly match. The regmust tests are costly enough +// that Compile() supplies a regmust only if the r.e. contains something +// potentially expensive (at present, the only such thing detected is * or + +// at the start of the r.e., which can involve a lot of backup). Regmlen is +// supplied because the test in RunMatcher() needs it and Compile() is computing +// it anyway. +// +// +// +// Structure for regexp "program". This is essentially a linear encoding +// of a nondeterministic finite-state machine (aka syntax charts or +// "railroad normal form" in parsing technology). Each node is an opcode +// plus a "next" pointer, possibly plus an operand. "Next" pointers of +// all nodes except kRegExpBranch implement concatenation; a "next" pointer with +// a kRegExpBranch on both ends of it is connecting two alternatives. (Here we +// have one of the subtle syntax dependencies: an individual kRegExpBranch (as +// opposed to a collection of them) is never concatenated with anything +// because of operator precedence.) The operand of some types of node is +// a literal string; for others, it is a node leading into a sub-FSM. In +// particular, the operand of a kRegExpBranch node is the first node of the branch. +// (NB this is *not* a tree structure: the tail of the branch connects +// to the thing following the set of kRegExpBranches.) The opcodes are: +// + +// definition number opnd? meaning +enum { + kRegExpEnd = 0, // no End of program. + kRegExpBol = 1, // no Match "" at beginning of line. + kRegExpEol = 2, // no Match "" at end of line. + kRegExpAny = 3, // no Match any one character. + kRegExpAnyOf = 4, // str Match any character in this string. + kRegExpAnyBut = 5, // str Match any character not in this string. + kRegExpBranch = 6, // node Match this alternative, or the next... + kRegExpBack = 7, // no Match "", "next" ptr points backward. + kRegExpExactly = 8, // str Match this string. + kRegExpNothing = 9, // no Match empty string. + kRegExpStar = 10, // node Match this (simple) thing 0 or more times. + kRegExpPlus = 11, // node Match this (simple) thing 1 or more times. + kRegExpOpen = 20, // no Mark this point in input as start of #n. + // kRegExpOpen + 1 is number 1, etc. + kRegExpClose = 30 // no Analogous to kRegExpOpen. +}; + +// +// Opcode notes: +// +// kRegExpBranch The set of branches constituting a single choice are hooked +// together with their "next" pointers, since precedence prevents +// anything being concatenated to any individual branch. The +// "next" pointer of the last kRegExpBranch in a choice points to the +// thing following the whole choice. This is also where the +// final "next" pointer of each individual branch points; each +// branch starts with the operand node of a kRegExpBranch node. +// +// kRegExpBack Normal "next" pointers all implicitly point forward; kRegExpBack +// exists to make loop structures possible. +// +// kRegExpStar,kRegExpPlus '?', and complex '*' and '+', are implemented as circular +// kRegExpBranch structures using kRegExpBack. Simple cases (one character +// per match) are implemented with kRegExpStar and kRegExpPlus for speed +// and to minimize recursive plunges. +// +// kRegExpOpen,kRegExpClose ...are numbered at compile time. +// +// +// +// A node is one char of opcode followed by two chars of "next" pointer. +// "Next" pointers are stored as two 8-bit pieces, high order first. The +// value is a positive offset from the opcode of the node containing it. +// An operand, if any, simply follows the node. (Note that much of the +// code generation knows about this implicit relationship.) +// +// Using two bytes for the "next" pointer is vast overkill for most things, +// but allows patterns to get big without disasters. +// + +const char *kMeta = "^$.[()|?+*\\"; +const int32 kMaxSize = 32767L; // Probably could be 65535L. + +// Flags to be passed up and down: +enum { + kHasWidth = 01, // Known never to match null string. + kSimple = 02, // Simple enough to be kRegExpStar/kRegExpPlus operand. + kSPStart = 04, // Starts with * or +. + kWorst = 0 // Worst case. +}; + +const char *kRegExpErrorStringArray[] = { + "Unmatched parenthesis.", + "Expression too long.", + "Too many parenthesis.", + "Junk on end.", + "*+? operand may be empty.", + "Nested *?+.", + "Invalid bracket range.", + "Unmatched brackets.", + "Internal error.", + "?+* follows nothing.", + "Trailing \\.", + "Corrupted expression.", + "Memory corruption.", + "Corrupted pointers.", + "Corrupted opcode." +}; + +#ifdef DEBUG +int32 regnarrate = 0; +#endif + +RegExp::RegExp() + : fError(B_OK), + fRegExp(NULL) +{ +} + +RegExp::RegExp(const char *pattern) + : fError(B_OK), + fRegExp(NULL) +{ + fRegExp = Compile(pattern); +} + +RegExp::RegExp(const BString &pattern) + : fError(B_OK), + fRegExp(NULL) +{ + fRegExp = Compile(pattern.String()); +} + +RegExp::~RegExp() +{ + free(fRegExp); +} + + + +status_t +RegExp::InitCheck() const +{ + return fError; +} + +status_t +RegExp::SetTo(const char *pattern) +{ + fError = B_OK; + free(fRegExp); + fRegExp = Compile(pattern); + return fError; +} + +status_t +RegExp::SetTo(const BString &pattern) +{ + fError = B_OK; + free(fRegExp); + fRegExp = Compile(pattern.String()); + return fError; +} + +bool +RegExp::Matches(const char *string) const +{ + if (!fRegExp || !string) + return false; + + return RunMatcher(fRegExp, string) == 1; +} + +bool +RegExp::Matches(const BString &string) const +{ + if (!fRegExp) + return false; + + return RunMatcher(fRegExp, string.String()) == 1; +} + + +// +// - Compile - compile a regular expression into internal code +// +// We can't allocate space until we know how big the compiled form will be, +// but we can't compile it (and thus know how big it is) until we've got a +// place to put the code. So we cheat: we compile it twice, once with code +// generation turned off and size counting turned on, and once "for real". +// This also means that we don't allocate space until we are sure that the +// thing really will compile successfully, and we never have to move the +// code and thus invalidate pointers into it. (Note that it has to be in +// one piece because free() must be able to free it all.) +// +// Beware that the optimization-preparation code in here knows about some +// of the structure of the compiled regexp. + +regexp * +RegExp::Compile(const char *exp) +{ + regexp *r; + const char *scan; + const char *longest; + int32 len; + int32 flags; + + if (exp == NULL) { + SetError(B_BAD_VALUE); + return NULL; + } + + // First pass: determine size, legality. + fInputScanPointer = exp; + fParenthesisCount = 1; + fCodeSize = 0L; + fCodeEmitPointer = &fDummy; + Char(kRegExpMagic); + if (Reg(0, &flags) == NULL) + return NULL; + + // Small enough for pointer-storage convention? + if (fCodeSize >= kMaxSize) { + SetError(REGEXP_TOO_BIG); + return NULL; + } + + // Allocate space. + r = (regexp *)malloc(sizeof(regexp) + fCodeSize); + + if (!r) { + SetError(B_NO_MEMORY); + return NULL; + } + + // Second pass: emit code. + fInputScanPointer = exp; + fParenthesisCount = 1; + fCodeEmitPointer = r->program; + Char(kRegExpMagic); + if (Reg(0, &flags) == NULL) { + free(r); + return NULL; + } + + // Dig out information for optimizations. + r->regstart = '\0'; // Worst-case defaults. + r->reganch = 0; + r->regmust = NULL; + r->regmlen = 0; + scan = r->program + 1; // First kRegExpBranch. + if (*Next((char *)scan) == kRegExpEnd) { // Only one top-level choice. + scan = Operand(scan); + + // Starting-point info. + if (*scan == kRegExpExactly) + r->regstart = *Operand(scan); + else if (*scan == kRegExpBol) + r->reganch++; + + // + // If there's something expensive in the r.e., find the + // longest literal string that must appear and make it the + // regmust. Resolve ties in favor of later strings, since + // the regstart check works with the beginning of the r.e. + // and avoiding duplication strengthens checking. Not a + // strong reason, but sufficient in the absence of others. + // + if (flags&kSPStart) { + longest = NULL; + len = 0; + for (; scan != NULL; scan = Next((char *)scan)) + if (*scan == kRegExpExactly && (int32)strlen(Operand(scan)) >= len) { + longest = Operand(scan); + len = (int32)strlen(Operand(scan)); + } + r->regmust = longest; + r->regmlen = len; + } + } + + return r; +} + +regexp * +RegExp::Expression() const +{ + return fRegExp; +} + +const char * +RegExp::ErrorString() const +{ + if (fError >= REGEXP_UNMATCHED_PARENTHESIS + && fError <= REGEXP_CORRUPTED_OPCODE) + return kRegExpErrorStringArray[fError - B_ERRORS_END]; + + return strerror(fError); +} + + +void +RegExp::SetError(status_t error) const +{ + fError = error; +} + + +// +// - Reg - regular expression, i.e. main body or parenthesized thing +// +// Caller must absorb opening parenthesis. +// +// Combining parenthesis handling with the base level of regular expression +// is a trifle forced, but the need to tie the tails of the branches to what +// follows makes it hard to avoid. +// +char * +RegExp::Reg(int32 paren, int32 *flagp) +{ + char *ret; + char *br; + char *ender; + int32 parno = 0; + int32 flags; + + *flagp = kHasWidth; // Tentatively. + + // Make an kRegExpOpen node, if parenthesized. + if (paren) { + if (fParenthesisCount >= kSubExpressionMax) { + SetError(REGEXP_TOO_MANY_PARENTHESIS); + return NULL; + } + parno = fParenthesisCount; + fParenthesisCount++; + ret = Node((char)(kRegExpOpen + parno)); + } else + ret = NULL; + + // Pick up the branches, linking them together. + br = Branch(&flags); + if (br == NULL) + return NULL; + if (ret != NULL) + Tail(ret, br); // kRegExpOpen -> first + else + ret = br; + if (!(flags & kHasWidth)) + *flagp &= ~kHasWidth; + *flagp |= flags&kSPStart; + while (*fInputScanPointer == '|') { + fInputScanPointer++; + br = Branch(&flags); + if (br == NULL) + return NULL; + Tail(ret, br); // kRegExpBranch -> kRegExpBranch. + if (!(flags & kHasWidth)) + *flagp &= ~kHasWidth; + *flagp |= flags&kSPStart; + } + + // Make a closing node, and hook it on the end. + ender = Node(paren ? (char)(kRegExpClose + parno) : (char)kRegExpEnd); + Tail(ret, ender); + + // Hook the tails of the branches to the closing node. + for (br = ret; br != NULL; br = Next(br)) + OpTail(br, ender); + + // Check for proper termination. + if (paren && *fInputScanPointer++ != ')') { + SetError(REGEXP_UNMATCHED_PARENTHESIS); + return NULL; + } else if (!paren && *fInputScanPointer != '\0') { + if (*fInputScanPointer == ')') { + SetError(REGEXP_UNMATCHED_PARENTHESIS); + return NULL; + } else { + SetError(REGEXP_JUNK_ON_END); + return NULL; // "Can't happen". + } + // NOTREACHED + } + + return ret; +} + +// +// - Branch - one alternative of an | operator +// +// Implements the concatenation operator. +// +char * +RegExp::Branch(int32 *flagp) +{ + char *ret; + char *chain; + char *latest; + int32 flags; + + *flagp = kWorst; // Tentatively. + + ret = Node(kRegExpBranch); + chain = NULL; + while (*fInputScanPointer != '\0' + && *fInputScanPointer != '|' + && *fInputScanPointer != ')') { + latest = Piece(&flags); + if (latest == NULL) + return NULL; + *flagp |= flags & kHasWidth; + if (chain == NULL) // First piece. + *flagp |= flags & kSPStart; + else + Tail(chain, latest); + chain = latest; + } + if (chain == NULL) // Loop ran zero times. + Node(kRegExpNothing); + + return ret; +} + +// +// - Piece - something followed by possible [*+?] +// +// Note that the branching code sequences used for ? and the general cases +// of * and + are somewhat optimized: they use the same kRegExpNothing node as +// both the endmarker for their branch list and the body of the last branch. +// It might seem that this node could be dispensed with entirely, but the +// endmarker role is not redundant. +// +char * +RegExp::Piece(int32 *flagp) +{ + char *ret; + char op; + char *next; + int32 flags; + + ret = Atom(&flags); + if (ret == NULL) + return NULL; + + op = *fInputScanPointer; + if (!IsMult(op)) { + *flagp = flags; + return ret; + } + + if (!(flags & kHasWidth) && op != '?') { + SetError(REGEXP_STAR_PLUS_OPERAND_EMPTY); + return NULL; + } + *flagp = op != '+' ? kWorst | kSPStart : kWorst | kHasWidth; + + if (op == '*' && (flags & kSimple)) + Insert(kRegExpStar, ret); + else if (op == '*') { + // Emit x* as (x&|), where & means "self". + Insert(kRegExpBranch, ret); // Either x + OpTail(ret, Node(kRegExpBack)); // and loop + OpTail(ret, ret); // back + Tail(ret, Node(kRegExpBranch)); // or + Tail(ret, Node(kRegExpNothing)); // null. + } else if (op == '+' && (flags & kSimple)) + Insert(kRegExpPlus, ret); + else if (op == '+') { + // Emit x+ as x(&|), where & means "self". + next = Node(kRegExpBranch); // Either + Tail(ret, next); + Tail(Node(kRegExpBack), ret); // loop back + Tail(next, Node(kRegExpBranch)); // or + Tail(ret, Node(kRegExpNothing)); // null. + } else if (op == '?') { + // Emit x? as (x|) + Insert(kRegExpBranch, ret); // Either x + Tail(ret, Node(kRegExpBranch)); // or + next = Node(kRegExpNothing); // null. + Tail(ret, next); + OpTail(ret, next); + } + fInputScanPointer++; + if (IsMult(*fInputScanPointer)) { + SetError(REGEXP_NESTED_STAR_QUESTION_PLUS); + return NULL; + } + return ret; +} + +// +// - Atom - the lowest level +// +// Optimization: gobbles an entire sequence of ordinary characters so that +// it can turn them into a single node, which is smaller to store and +// faster to run. Backslashed characters are exceptions, each becoming a +// separate node; the code is simpler that way and it's not worth fixing. +// +char * +RegExp::Atom(int32 *flagp) +{ + char *ret; + int32 flags; + + *flagp = kWorst; // Tentatively. + + switch (*fInputScanPointer++) { + case '^': + ret = Node(kRegExpBol); + break; + case '$': + ret = Node(kRegExpEol); + break; + case '.': + ret = Node(kRegExpAny); + *flagp |= kHasWidth|kSimple; + break; + case '[': + { + int32 cclass; + int32 classend; + + if (*fInputScanPointer == '^') { // Complement of range. + ret = Node(kRegExpAnyBut); + fInputScanPointer++; + } else + ret = Node(kRegExpAnyOf); + if (*fInputScanPointer == ']' || *fInputScanPointer == '-') + Char(*fInputScanPointer++); + while (*fInputScanPointer != '\0' && *fInputScanPointer != ']') { + if (*fInputScanPointer == '-') { + fInputScanPointer++; + if (*fInputScanPointer == ']' || *fInputScanPointer == '\0') + Char('-'); + else { + cclass = UCharAt(fInputScanPointer - 2) + 1; + classend = UCharAt(fInputScanPointer); + if (cclass > classend + 1) { + SetError(REGEXP_INVALID_BRACKET_RANGE); + return NULL; + } + for (; cclass <= classend; cclass++) + Char((char)cclass); + fInputScanPointer++; + } + } else + Char(*fInputScanPointer++); + } + Char('\0'); + if (*fInputScanPointer != ']') { + SetError(REGEXP_UNMATCHED_BRACKET); + return NULL; + } + fInputScanPointer++; + *flagp |= kHasWidth | kSimple; + } + break; + case '(': + ret = Reg(1, &flags); + if (ret == NULL) + return NULL; + *flagp |= flags & (kHasWidth | kSPStart); + break; + case '\0': + case '|': + case ')': + SetError(REGEXP_INTERNAL_ERROR); + return NULL; // Supposed to be caught earlier. + case '?': + case '+': + case '*': + SetError(REGEXP_QUESTION_PLUS_STAR_FOLLOWS_NOTHING); + return NULL; + case '\\': + if (*fInputScanPointer == '\0') { + SetError(REGEXP_TRAILING_BACKSLASH); + return NULL; + } + ret = Node(kRegExpExactly); + Char(*fInputScanPointer++); + Char('\0'); + *flagp |= kHasWidth|kSimple; + break; + default: + { + int32 len; + char ender; + + fInputScanPointer--; + len = (int32)strcspn(fInputScanPointer, kMeta); + if (len <= 0) { + SetError(REGEXP_INTERNAL_ERROR); + return NULL; + } + ender = *(fInputScanPointer + len); + if (len > 1 && IsMult(ender)) + len--; // Back off clear of ?+* operand. + *flagp |= kHasWidth; + if (len == 1) + *flagp |= kSimple; + ret = Node(kRegExpExactly); + while (len > 0) { + Char(*fInputScanPointer++); + len--; + } + Char('\0'); + } + break; + } + + return ret; +} + +// +// - Node - emit a node +// +char * // Location. +RegExp::Node(char op) +{ + char *ret; + char *ptr; + + ret = fCodeEmitPointer; + if (ret == &fDummy) { + fCodeSize += 3; + return ret; + } + + ptr = ret; + *ptr++ = op; + *ptr++ = '\0'; // Null "next" pointer. + *ptr++ = '\0'; + fCodeEmitPointer = ptr; + + return ret; +} + +// +// - Char - emit (if appropriate) a byte of code +// +void +RegExp::Char(char b) +{ + if (fCodeEmitPointer != &fDummy) + *fCodeEmitPointer++ = b; + else + fCodeSize++; +} + +// +// - Insert - insert an operator in front of already-emitted operand +// +// Means relocating the operand. +// +void +RegExp::Insert(char op, char *opnd) +{ + char *src; + char *dst; + char *place; + + if (fCodeEmitPointer == &fDummy) { + fCodeSize += 3; + return; + } + + src = fCodeEmitPointer; + fCodeEmitPointer += 3; + dst = fCodeEmitPointer; + while (src > opnd) + *--dst = *--src; + + place = opnd; // Op node, where operand used to be. + *place++ = op; + *place++ = '\0'; + *place++ = '\0'; +} + +// +// - Tail - set the next-pointer at the end of a node chain +// +void +RegExp::Tail(char *p, char *val) +{ + char *scan; + char *temp; + int32 offset; + + if (p == &fDummy) + return; + + // Find last node. + scan = p; + for (;;) { + temp = Next(scan); + if (temp == NULL) + break; + scan = temp; + } + + if (scan[0] == kRegExpBack) + offset = scan - val; + else + offset = val - scan; + + scan[1] = (char)((offset >> 8) & 0377); + scan[2] = (char)(offset & 0377); +} + +// +// - OpTail - Tail on operand of first argument; nop if operandless +// +void +RegExp::OpTail(char *p, char *val) +{ + // "Operandless" and "op != kRegExpBranch" are synonymous in practice. + if (p == NULL || p == &fDummy || *p != kRegExpBranch) + return; + Tail(Operand(p), val); +} + +// +// RunMatcher and friends +// + +// +// - RunMatcher - match a regexp against a string +// +int32 +RegExp::RunMatcher(regexp *prog, const char *string) const +{ + const char *s; + + // Be paranoid... + if (prog == NULL || string == NULL) { + SetError(B_BAD_VALUE); + return 0; + } + + // Check validity of program. + if (UCharAt(prog->program) != kRegExpMagic) { + SetError(REGEXP_CORRUPTED_PROGRAM); + return 0; + } + + // If there is a "must appear" string, look for it. + if (prog->regmust != NULL) { + s = string; + while ((s = strchr(s, prog->regmust[0])) != NULL) { + if (strncmp(s, prog->regmust, (size_t)prog->regmlen) == 0) + break; // Found it. + s++; + } + if (s == NULL) // Not present. + return 0; + } + + // Mark beginning of line for ^ . + fRegBol = string; + + // Simplest case: anchored match need be tried only once. + if (prog->reganch) + return Try(prog, (char*)string); + + // Messy cases: unanchored match. + s = string; + if (prog->regstart != '\0') + // We know what char it must start with. + while ((s = strchr(s, prog->regstart)) != NULL) { + if (Try(prog, (char*)s)) + return 1; + s++; + } + else + // We don't -- general case. + do { + if (Try(prog, (char*)s)) + return 1; + } while (*s++ != '\0'); + + // Failure. + return 0; +} + +// +// - Try - try match at specific point +// +int32 // 0 failure, 1 success +RegExp::Try(regexp *prog, const char *string) const +{ + int32 i; + const char **sp; + const char **ep; + + fStringInputPointer = string; + fStartPArrayPointer = prog->startp; + fEndPArrayPointer = prog->endp; + + sp = prog->startp; + ep = prog->endp; + for (i = kSubExpressionMax; i > 0; i--) { + *sp++ = NULL; + *ep++ = NULL; + } + if (Match(prog->program + 1)) { + prog->startp[0] = string; + prog->endp[0] = fStringInputPointer; + return 1; + } else + return 0; +} + +// +// - Match - main matching routine +// +// Conceptually the strategy is simple: check to see whether the current +// node matches, call self recursively to see whether the rest matches, +// and then act accordingly. In practice we make some effort to avoid +// recursion, in particular by going through "ordinary" nodes (that don't +// need to know whether the rest of the match failed) by a loop instead of +// by recursion. +/// +int32 // 0 failure, 1 success +RegExp::Match(const char *prog) const +{ + const char *scan; // Current node. + const char *next; // Next node. + + scan = prog; +#ifdef DEBUG + if (scan != NULL && regnarrate) + fprintf(stderr, "%s(\n", Prop(scan)); +#endif + while (scan != NULL) { +#ifdef DEBUG + if (regnarrate) + fprintf(stderr, "%s...\n", Prop(scan)); +#endif + next = Next(scan); + + switch (*scan) { + case kRegExpBol: + if (fStringInputPointer != fRegBol) + return 0; + break; + case kRegExpEol: + if (*fStringInputPointer != '\0') + return 0; + break; + case kRegExpAny: + if (*fStringInputPointer == '\0') + return 0; + fStringInputPointer++; + break; + case kRegExpExactly: + { + const char *opnd = Operand(scan); + // Inline the first character, for speed. + if (*opnd != *fStringInputPointer) + return 0; + + uint32 len = strlen(opnd); + if (len > 1 && strncmp(opnd, fStringInputPointer, len) != 0) + return 0; + + fStringInputPointer += len; + } + break; + case kRegExpAnyOf: + if (*fStringInputPointer == '\0' + || strchr(Operand(scan), *fStringInputPointer) == NULL) + return 0; + fStringInputPointer++; + break; + case kRegExpAnyBut: + if (*fStringInputPointer == '\0' + || strchr(Operand(scan), *fStringInputPointer) != NULL) + return 0; + fStringInputPointer++; + break; + case kRegExpNothing: + break; + case kRegExpBack: + break; + case kRegExpOpen + 1: + case kRegExpOpen + 2: + case kRegExpOpen + 3: + case kRegExpOpen + 4: + case kRegExpOpen + 5: + case kRegExpOpen + 6: + case kRegExpOpen + 7: + case kRegExpOpen + 8: + case kRegExpOpen + 9: + { + int32 no; + const char *save; + + no = *scan - kRegExpOpen; + save = fStringInputPointer; + + if (Match(next)) { + // + // Don't set startp if some later + // invocation of the same parentheses + // already has. + // + if (fStartPArrayPointer[no] == NULL) + fStartPArrayPointer[no] = save; + return 1; + } else + return 0; + } + break; + case kRegExpClose + 1: + case kRegExpClose + 2: + case kRegExpClose + 3: + case kRegExpClose + 4: + case kRegExpClose + 5: + case kRegExpClose + 6: + case kRegExpClose + 7: + case kRegExpClose + 8: + case kRegExpClose + 9: + { + int32 no; + const char *save; + + no = *scan - kRegExpClose; + save = fStringInputPointer; + + if (Match(next)) { + // + // Don't set endp if some later + // invocation of the same parentheses + // already has. + // + if (fEndPArrayPointer[no] == NULL) + fEndPArrayPointer[no] = save; + return 1; + } else + return 0; + } + break; + case kRegExpBranch: + { + const char *save; + + if (*next != kRegExpBranch) // No choice. + next = Operand(scan); // Avoid recursion. + else { + do { + save = fStringInputPointer; + if (Match(Operand(scan))) + return 1; + fStringInputPointer = save; + scan = Next(scan); + } while (scan != NULL && *scan == kRegExpBranch); + return 0; + // NOTREACHED/ + } + } + break; + case kRegExpStar: + case kRegExpPlus: + { + char nextch; + int32 no; + const char *save; + int32 min; + + // + //Lookahead to avoid useless match attempts + // when we know what character comes next. + // + nextch = '\0'; + if (*next == kRegExpExactly) + nextch = *Operand(next); + min = (*scan == kRegExpStar) ? 0 : 1; + save = fStringInputPointer; + no = Repeat(Operand(scan)); + while (no >= min) { + // If it could work, try it. + if (nextch == '\0' || *fStringInputPointer == nextch) + if (Match(next)) + return 1; + // Couldn't or didn't -- back up. + no--; + fStringInputPointer = save + no; + } + return 0; + } + break; + case kRegExpEnd: + return 1; // Success! + + default: + SetError(REGEXP_MEMORY_CORRUPTION); + return 0; + } + + scan = next; + } + + // + // We get here only if there's trouble -- normally "case kRegExpEnd" is + // the terminating point. + // + SetError(REGEXP_CORRUPTED_POINTERS); + return 0; +} + +// +// - Repeat - repeatedly match something simple, report how many +// +int32 +RegExp::Repeat(const char *p) const +{ + int32 count = 0; + const char *scan; + const char *opnd; + + scan = fStringInputPointer; + opnd = Operand(p); + switch (*p) { + case kRegExpAny: + count = (int32)strlen(scan); + scan += count; + break; + + case kRegExpExactly: + while (*opnd == *scan) { + count++; + scan++; + } + break; + + case kRegExpAnyOf: + while (*scan != '\0' && strchr(opnd, *scan) != NULL) { + count++; + scan++; + } + break; + + case kRegExpAnyBut: + while (*scan != '\0' && strchr(opnd, *scan) == NULL) { + count++; + scan++; + } + break; + + default: // Oh dear. Called inappropriately. + SetError(REGEXP_INTERNAL_ERROR); + count = 0; // Best compromise. + break; + } + fStringInputPointer = scan; + + return count; +} + +// +// - Next - dig the "next" pointer out of a node +// +char * +RegExp::Next(char *p) +{ + int32 offset; + + if (p == &fDummy) + return NULL; + + offset = ((*(p + 1) & 0377) << 8) + (*(p + 2) & 0377); + if (offset == 0) + return NULL; + + if (*p == kRegExpBack) + return p - offset; + else + return p + offset; +} + +const char * +RegExp::Next(const char *p) const +{ + int32 offset; + + if (p == &fDummy) + return NULL; + + offset = ((*(p + 1) & 0377) << 8) + (*(p + 2) & 0377); + if (offset == 0) + return NULL; + + if (*p == kRegExpBack) + return p - offset; + else + return p + offset; +} + +inline int32 +RegExp::UCharAt(const char *p) const +{ + return (int32)*(unsigned char *)p; +} + +inline char * +RegExp::Operand(char* p) const +{ + return p + 3; +} + +inline const char * +RegExp::Operand(const char* p) const +{ + return p + 3; +} + +inline bool +RegExp::IsMult(char c) const +{ + return c == '*' || c == '+' || c == '?'; +} + + +#ifdef DEBUG + +// +// - Dump - dump a regexp onto stdout in vaguely comprehensible form +// +void +RegExp::Dump() +{ + const char *s; + char op = kRegExpExactly; // Arbitrary non-kRegExpEnd op. + const char *next; + + s = fRegExp->program + 1; + while (op != kRegExpEnd) { // While that wasn't kRegExpEnd last time... + op = *s; + printf("%2ld%s", s - fRegExp->program, Prop(s)); // Where, what. + next = Next(s); + if (next == NULL) // Next ptr. + printf("(0)"); + else + printf("(%ld)", (s - fRegExp->program) + (next - s)); + s += 3; + if (op == kRegExpAnyOf || op == kRegExpAnyBut || op == kRegExpExactly) { + // Literal string, where present. + while (*s != '\0') { + putchar(*s); + s++; + } + s++; + } + putchar('\n'); + } + + // Header fields of interest. + if (fRegExp->regstart != '\0') + printf("start `%c' ", fRegExp->regstart); + if (fRegExp->reganch) + printf("anchored "); + if (fRegExp->regmust != NULL) + printf("must have \"%s\"", fRegExp->regmust); + printf("\n"); +} + +// +// - Prop - printable representation of opcode +// +char * +RegExp::Prop(const char *op) const +{ + char *p = NULL; + static char buf[50]; + + (void) strcpy(buf, ":"); + + switch (*op) { + case kRegExpBol: + p = "kRegExpBol"; + break; + case kRegExpEol: + p = "kRegExpEol"; + break; + case kRegExpAny: + p = "kRegExpAny"; + break; + case kRegExpAnyOf: + p = "kRegExpAnyOf"; + break; + case kRegExpAnyBut: + p = "kRegExpAnyBut"; + break; + case kRegExpBranch: + p = "kRegExpBranch"; + break; + case kRegExpExactly: + p = "kRegExpExactly"; + break; + case kRegExpNothing: + p = "kRegExpNothing"; + break; + case kRegExpBack: + p = "kRegExpBack"; + break; + case kRegExpEnd: + p = "kRegExpEnd"; + break; + case kRegExpOpen + 1: + case kRegExpOpen + 2: + case kRegExpOpen + 3: + case kRegExpOpen + 4: + case kRegExpOpen + 5: + case kRegExpOpen + 6: + case kRegExpOpen + 7: + case kRegExpOpen + 8: + case kRegExpOpen + 9: + sprintf(buf + strlen(buf), "kRegExpOpen%d", *op - kRegExpOpen); + p = NULL; + break; + case kRegExpClose + 1: + case kRegExpClose + 2: + case kRegExpClose + 3: + case kRegExpClose + 4: + case kRegExpClose + 5: + case kRegExpClose + 6: + case kRegExpClose + 7: + case kRegExpClose + 8: + case kRegExpClose + 9: + sprintf(buf + strlen(buf), "kRegExpClose%d", *op - kRegExpClose); + p = NULL; + break; + case kRegExpStar: + p = "kRegExpStar"; + break; + case kRegExpPlus: + p = "kRegExpPlus"; + break; + default: + RegExpError("corrupted opcode"); + break; + } + + if (p != NULL) + strcat(buf, p); + + return buf; +} + +void +RegExp::RegExpError(const char *) const +{ + // does nothing now, perhaps it should printf? +} + +#endif diff --git a/src/kits/tracker/RegExp.h b/src/kits/tracker/RegExp.h new file mode 100644 index 0000000000..509f496768 --- /dev/null +++ b/src/kits/tracker/RegExp.h @@ -0,0 +1,184 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + + +// This code is based on regexp.c, v.1.3 by Henry Spencer: + +// @(#)regexp.c 1.3 of 18 April 87 +// +// Copyright (c) 1986 by University of Toronto. +// Written by Henry Spencer. Not derived from licensed software. +// +// Permission is granted to anyone to use this software for any +// purpose on any computer system, and to redistribute it freely, +// subject to the following restrictions: +// +// 1. The author is not responsible for the consequences of use of +// this software, no matter how awful, even if they arise +// from defects in it. +// +// 2. The origin of this software must not be misrepresented, either +// by explicit claim or by omission. +// +// 3. Altered versions must be plainly marked as such, and must not +// be misrepresented as being the original software. +// +// Beware that some of this code is subtly aware of the way operator +// precedence is structured in regular expressions. Serious changes in +// regular-expression syntax might require a total rethink. +// + +// ALTERED VERSION: Adapted to ANSI C and C++ for the OpenTracker +// project (www.opentracker.org), Jul 11, 2000. + +#ifndef _REG_EXP_H +#define _REG_EXP_H + +#include + +namespace BPrivate { + +enum { + REGEXP_UNMATCHED_PARENTHESIS = B_ERRORS_END, + REGEXP_TOO_BIG, + REGEXP_TOO_MANY_PARENTHESIS, + REGEXP_JUNK_ON_END, + REGEXP_STAR_PLUS_OPERAND_EMPTY, + REGEXP_NESTED_STAR_QUESTION_PLUS, + REGEXP_INVALID_BRACKET_RANGE, + REGEXP_UNMATCHED_BRACKET, + REGEXP_INTERNAL_ERROR, + REGEXP_QUESTION_PLUS_STAR_FOLLOWS_NOTHING, + REGEXP_TRAILING_BACKSLASH, + REGEXP_CORRUPTED_PROGRAM, + REGEXP_MEMORY_CORRUPTION, + REGEXP_CORRUPTED_POINTERS, + REGEXP_CORRUPTED_OPCODE +}; + +const int32 kSubExpressionMax = 10; + +struct regexp { + const char *startp[kSubExpressionMax]; + const char *endp[kSubExpressionMax]; + char regstart; /* Internal use only. See RegExp.cpp for details. */ + char reganch; /* Internal use only. */ + const char *regmust;/* Internal use only. */ + int regmlen; /* Internal use only. */ + char program[1]; /* Unwarranted chumminess with compiler. */ +}; + +class RegExp { + +public: + RegExp(); + RegExp(const char *); + RegExp(const BString &); + ~RegExp(); + + status_t InitCheck() const; + + status_t SetTo(const char*); + status_t SetTo(const BString &); + + bool Matches(const char *string) const; + bool Matches(const BString &) const; + + int32 RunMatcher(regexp *, const char *) const; + regexp *Compile(const char *); + regexp *Expression() const; + const char *ErrorString() const; + +#ifdef DEBUG + void Dump(); +#endif + +private: + + void SetError(status_t error) const; + + // Working functions for Compile(): + char *Reg(int32, int32 *); + char *Branch(int32 *); + char *Piece(int32 *); + char *Atom(int32 *); + char *Node(char); + char *Next(char *); + const char *Next(const char *) const; + void Char(char); + void Insert(char, char *); + void Tail(char *, char *); + void OpTail(char *, char *); + + // Working functions for RunMatcher(): + int32 Try(regexp *, const char *) const; + int32 Match(const char *) const; + int32 Repeat(const char *) const; + + // Utility functions: +#ifdef DEBUG + char *Prop(const char *) const; + void RegExpError(const char *) const; +#endif + inline int32 UCharAt(const char *p) const; + inline char *Operand(char* p) const; + inline const char *Operand(const char* p) const; + inline bool IsMult(char c) const; + +// --------- Variables ------------- + + mutable status_t fError; + regexp *fRegExp; + + // Work variables for Compile(). + + const char *fInputScanPointer; + int32 fParenthesisCount; + char fDummy; + char *fCodeEmitPointer; // &fDummy = don't. + long fCodeSize; + + // Work variables for RunMatcher(). + + mutable const char *fStringInputPointer; + mutable const char *fRegBol; // Beginning of input, for ^ check. + mutable const char **fStartPArrayPointer; + mutable const char **fEndPArrayPointer; +}; + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/SelectionWindow.cpp b/src/kits/tracker/SelectionWindow.cpp new file mode 100644 index 0000000000..1a33093317 --- /dev/null +++ b/src/kits/tracker/SelectionWindow.cpp @@ -0,0 +1,274 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include +#include +#include +#include + +#include "AutoLock.h" +#include "ContainerWindow.h" +#include "Commands.h" +#include "Screen.h" +#include "SelectionWindow.h" + +const int frameThickness = 9; + +const uint32 kSelectButtonPressed = 'sbpr'; + +SelectionWindow::SelectionWindow(BContainerWindow *window) + : BWindow(BRect(0, 0, 270, 0), + "Select", B_TITLED_WINDOW, + B_NOT_ZOOMABLE | B_NOT_MINIMIZABLE | B_NOT_V_RESIZABLE + | B_NO_WORKSPACE_ACTIVATION | B_ASYNCHRONOUS_CONTROLS + | B_NOT_ANCHORED_ON_ACTIVATE), + fParentWindow(window) +{ + if (window->Feel() & kPrivateDesktopWindowFeel) + // The window will not show up if we have B_FLOATING_SUBSET_WINDOW_FEEL + // and use it with the desktop window since it's never in front. + SetFeel(B_NORMAL_WINDOW_FEEL); + + AddToSubset(fParentWindow); + + BRect backgroundRect = Bounds(); + backgroundRect.InsetBy(-1, -1); + BView *backgroundView = new BBox(backgroundRect, "bgView", B_FOLLOW_ALL); + AddChild(backgroundView); + + BMenu *menu = new BPopUpMenu(""); + + menu->AddItem(new BMenuItem("starts with", NULL)); + menu->AddItem(new BMenuItem("ends with", NULL)); + menu->AddItem(new BMenuItem("contains", NULL)); + menu->AddItem(new BMenuItem("matches wildcard expression", NULL)); + menu->AddItem(new BMenuItem("matches regular expression", NULL)); + + menu->SetLabelFromMarked(true); + menu->ItemAt(3)->SetMarked(true); + // Set wildcard matching to default. + + // Set up the menu field + fMatchingTypeMenuField = new BMenuField(BRect(7, 6, Bounds().right - 5, 0), + NULL, "Name", menu); + backgroundView->AddChild(fMatchingTypeMenuField); + fMatchingTypeMenuField->SetDivider(fMatchingTypeMenuField->StringWidth("Name") + 8); + fMatchingTypeMenuField->ResizeToPreferred(); + + // Set up the expression text control + fExpressionTextControl = new BTextControl(BRect(7, fMatchingTypeMenuField-> + Bounds().bottom + 11, Bounds().right - 6, 0), NULL, NULL, NULL, NULL, + B_FOLLOW_LEFT_RIGHT); + backgroundView->AddChild(fExpressionTextControl); + fExpressionTextControl->ResizeToPreferred(); + fExpressionTextControl->MakeFocus(true); + + // Set up the Invert checkbox + fInverseCheckBox = new BCheckBox(BRect(7, fExpressionTextControl->Frame().bottom + + 6, 6, 6), NULL, "Invert", NULL); + backgroundView->AddChild(fInverseCheckBox); + fInverseCheckBox->ResizeToPreferred(); + + // Set up the Ignore Case checkbox + fIgnoreCaseCheckBox = new BCheckBox(BRect(fInverseCheckBox->Frame().right + 10, + fInverseCheckBox->Frame().top, 6, 6), NULL, "Ignore case", NULL); + fIgnoreCaseCheckBox->SetValue(1); + backgroundView->AddChild(fIgnoreCaseCheckBox); + fIgnoreCaseCheckBox->ResizeToPreferred(); + + // Set up the Select button + fSelectButton = new BButton(BRect(0, 0, 5, 5), NULL, "Select", + new BMessage(kSelectButtonPressed), B_FOLLOW_RIGHT); + + backgroundView->AddChild(fSelectButton); + fSelectButton->ResizeToPreferred(); + fSelectButton->MoveTo(Bounds().right - 10 - fSelectButton->Bounds().right, + fExpressionTextControl->Frame().bottom + 9); + fSelectButton->MakeDefault(true); + #if !B_BEOS_VERSION_DANO + fSelectButton->SetLowColor(backgroundView->ViewColor()); + fSelectButton->SetViewColor(B_TRANSPARENT_COLOR); + #endif + + font_height fh; + be_plain_font->GetHeight(&fh); + // Center the checkboxes vertically to the button + float topMiddleButton = + (fSelectButton->Bounds().Height() / 2 - + (fh.ascent + fh.descent + fh.leading + 4) / 2) + fSelectButton->Frame().top; + fInverseCheckBox->MoveTo(fInverseCheckBox->Frame().left, topMiddleButton); + fIgnoreCaseCheckBox->MoveTo(fIgnoreCaseCheckBox->Frame().left, topMiddleButton); + + float bottomMinWidth = 32 + fSelectButton->Bounds().Width() + + fInverseCheckBox->Bounds().Width() + fIgnoreCaseCheckBox->Bounds().Width(); + float topMinWidth = be_plain_font->StringWidth("Name matches wildcard expression:###"); + float minWidth = bottomMinWidth > topMinWidth ? bottomMinWidth : topMinWidth; + + Run(); + + Lock(); + ResizeTo(minWidth, fSelectButton->Frame().bottom + 6); + + SetSizeLimits( + /* Minimum Width */ minWidth, + /* Maximum Width */ 1280, + /* Minimum Height */ Bounds().bottom, + /* Maximum Height */ Bounds().bottom); + + MoveCloseToMouse(); + Unlock(); +} + +void +SelectionWindow::MessageReceived(BMessage *message) +{ + switch (message->what) { + case kSelectButtonPressed: + { + Hide(); + // Order of posting and hiding important + // since we want to activate the target + // window when the message arrives. + // (Hide is synhcronous, while PostMessage is not.) + // See PoseView::SelectMatchingEntries(). + + BMessage *selectionInfo = new BMessage(kSelectMatchingEntries); + selectionInfo->AddInt32("ExpressionType", ExpressionType()); + BString expression; + Expression(expression); + selectionInfo->AddString("Expression", expression.String()); + selectionInfo->AddBool("InvertSelection", Invert()); + selectionInfo->AddBool("IgnoreCase", IgnoreCase()); + fParentWindow->PostMessage(selectionInfo); + } + break; + + default: + _inherited::MessageReceived(message); + } +} + +bool +SelectionWindow::QuitRequested() +{ + Hide(); + return false; +} + +void +SelectionWindow::MoveCloseToMouse() +{ + uint32 buttons; + BPoint mousePosition; + + ChildAt((int32)0)->GetMouse(&mousePosition, &buttons); + ConvertToScreen(&mousePosition); + + // Position the window centered around the mouse... + BPoint windowPosition = BPoint(mousePosition.x - Frame().Width() / 2, + mousePosition.y - Frame().Height() / 2); + + // ... unless that's outside of the current screen size: + BScreen screen; + windowPosition.x = MAX(0, MIN(screen.Frame().right - Frame().Width(), + windowPosition.x)); + windowPosition.y = MAX(0, MIN(screen.Frame().bottom - Frame().Height(), + windowPosition.y)); + + MoveTo(windowPosition); +} + + + +TrackerStringExpressionType +SelectionWindow::ExpressionType() const +{ + if (!fMatchingTypeMenuField->LockLooper()) + return kNone; + + BMenuItem *item = fMatchingTypeMenuField->Menu()->FindMarked(); + if (!item) { + fMatchingTypeMenuField->UnlockLooper(); + return kNone; + } + + int32 index = fMatchingTypeMenuField->Menu()->IndexOf(item); + + fMatchingTypeMenuField->UnlockLooper(); + + if (index < kStartsWith || index > kRegexpMatch) + return kNone; + + TrackerStringExpressionType typeArray[] = { kStartsWith, kEndsWith, + kContains, kGlobMatch, kRegexpMatch}; + + return typeArray[index]; +} + +void +SelectionWindow::Expression(BString &result) const +{ + if (!fExpressionTextControl->LockLooper()) + return; + + result = fExpressionTextControl->Text(); + + fExpressionTextControl->UnlockLooper(); +} + +bool +SelectionWindow::IgnoreCase() const +{ + if (!fIgnoreCaseCheckBox->LockLooper()) + return true; // default action. + + bool ignore = fIgnoreCaseCheckBox->Value() != 0; + + fIgnoreCaseCheckBox->UnlockLooper(); + + return ignore; +} + +bool +SelectionWindow::Invert() const +{ + if (!fInverseCheckBox->LockLooper()) + return false; // default action. + + bool inverse = fInverseCheckBox->Value() != 0; + + fInverseCheckBox->UnlockLooper(); + + return inverse; +} diff --git a/src/kits/tracker/SelectionWindow.h b/src/kits/tracker/SelectionWindow.h new file mode 100644 index 0000000000..2ac72d35fa --- /dev/null +++ b/src/kits/tracker/SelectionWindow.h @@ -0,0 +1,81 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef _SELECTION_WINDOW_H +#define _SELECTION_WINDOW_H + +#include +#include +#include +#include +#include +#include + +#include "TrackerString.h" + +namespace BPrivate { + +class BContainerWindow; + +class SelectionWindow : public BWindow { +public: + SelectionWindow(BContainerWindow *); + + void MessageReceived(BMessage *); + bool QuitRequested(); + + void MoveCloseToMouse(); + + TrackerStringExpressionType ExpressionType() const; + void Expression(BString &result) const; + bool IgnoreCase() const; + bool Invert() const; + +private: + BContainerWindow *fParentWindow; + + BMenuField *fMatchingTypeMenuField; + BTextControl *fExpressionTextControl; + BCheckBox *fInverseCheckBox; + BCheckBox *fIgnoreCaseCheckBox; + BButton *fSelectButton; + + typedef BWindow _inherited; +}; + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/Settings.cpp b/src/kits/tracker/Settings.cpp new file mode 100644 index 0000000000..c67c2f0805 --- /dev/null +++ b/src/kits/tracker/Settings.cpp @@ -0,0 +1,276 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include + +#include +#include +#include + +#include "TrackerSettings.h" + +Settings *settings = NULL; + +// generic setting handler classes + +StringValueSetting::StringValueSetting(const char *name, const char *defaultValue, + const char *valueExpectedErrorString, const char *wrongValueErrorString) + : SettingsArgvDispatcher(name), + fDefaultValue(defaultValue), + fValueExpectedErrorString(valueExpectedErrorString), + fWrongValueErrorString(wrongValueErrorString), + fValue(defaultValue) +{ +} + +StringValueSetting::~StringValueSetting() +{ +} + +void +StringValueSetting::ValueChanged(const char *newValue) +{ + fValue = newValue; +} + +const char * +StringValueSetting::Value() const +{ + return fValue.String(); +} + +void +StringValueSetting::SaveSettingValue(Settings *settings) +{ + settings->Write("\"%s\"", fValue.String()); +} + +bool +StringValueSetting::NeedsSaving() const +{ + // needs saving if different than default + return fValue != fDefaultValue; +} + +const char * +StringValueSetting::Handle(const char *const *argv) +{ + if (!*++argv) + return fValueExpectedErrorString; + + ValueChanged(*argv); + return 0; +} + +// #pragma mark - + +EnumeratedStringValueSetting::EnumeratedStringValueSetting(const char *name, + const char *defaultValue, const char *const *values, const char *valueExpectedErrorString, + const char *wrongValueErrorString) + : StringValueSetting(name, defaultValue, valueExpectedErrorString, wrongValueErrorString), + fValues(values) +{ +} + +void +EnumeratedStringValueSetting::ValueChanged(const char *newValue) +{ +#if DEBUG + // must be one of the enumerated values + bool found = false; + for (int32 index = 0; ; index++) { + if (!fValues[index]) + break; + if (strcmp(fValues[index], newValue) != 0) + continue; + found = true; + break; + } + ASSERT(found); +#endif + StringValueSetting::ValueChanged(newValue); +} + +const char * +EnumeratedStringValueSetting::Handle(const char *const *argv) +{ + if (!*++argv) + return fValueExpectedErrorString; + + bool found = false; + for (int32 index = 0; ; index++) { + if (!fValues[index]) + break; + if (strcmp(fValues[index], *argv) != 0) + continue; + found = true; + break; + } + + if (!found) + return fWrongValueErrorString; + + ValueChanged(*argv); + return 0; +} + +// #pragma mark - + +ScalarValueSetting::ScalarValueSetting(const char *name, int32 defaultValue, + const char *valueExpectedErrorString, const char *wrongValueErrorString, + int32 min, int32 max) + : SettingsArgvDispatcher(name), + fDefaultValue(defaultValue), + fValue(defaultValue), + fMax(max), + fMin(min), + fValueExpectedErrorString(valueExpectedErrorString), + fWrongValueErrorString(wrongValueErrorString) +{ +} + +void +ScalarValueSetting::ValueChanged(int32 newValue) +{ + ASSERT(newValue > fMin); + ASSERT(newValue < fMax); + fValue = newValue; +} + +int32 +ScalarValueSetting::Value() const +{ + return fValue; +} + +void +ScalarValueSetting::GetValueAsString(char *buffer) const +{ + sprintf(buffer, "%ld", fValue); +} + +const char * +ScalarValueSetting::Handle(const char *const *argv) +{ + if (!*++argv) + return fValueExpectedErrorString; + + int32 newValue; + if ((*argv)[0] == '0' && (*argv)[1] == 'x') + sscanf(*argv,"%lx",&newValue); + else + newValue = atoi(*argv); + + if (newValue < fMin || newValue > fMax) + return fWrongValueErrorString; + + fValue = newValue; + return NULL; +} + +void +ScalarValueSetting::SaveSettingValue(Settings *settings) +{ + settings->Write("%ld", fValue); +} + +bool +ScalarValueSetting::NeedsSaving() const +{ + return fValue != fDefaultValue; +} + +// #pragma mark - + +HexScalarValueSetting::HexScalarValueSetting(const char *name, int32 defaultValue, + const char *valueExpectedErrorString, const char *wrongValueErrorString, + int32 min, int32 max) + : ScalarValueSetting(name, defaultValue, valueExpectedErrorString, + wrongValueErrorString, min, max) +{ +} + +void +HexScalarValueSetting::GetValueAsString(char *buffer) const +{ + sprintf(buffer, "0x%08lx", fValue); +} + +void +HexScalarValueSetting::SaveSettingValue(Settings *settings) +{ + settings->Write("0x%08lx", fValue); +} + +// #pragma mark - + +BooleanValueSetting::BooleanValueSetting(const char *name, bool defaultValue) + : ScalarValueSetting(name, defaultValue, 0, 0) +{ +} + +bool +BooleanValueSetting::Value() const +{ + return fValue != 0; +} + +void +BooleanValueSetting::SetValue(bool value) +{ + fValue = value; +} + +const char * +BooleanValueSetting::Handle(const char *const *argv) +{ + if (!*++argv) + return "on or off expected"; + + if (strcmp(*argv, "on") == 0) + fValue = true; + else if (strcmp(*argv, "off") == 0) + fValue = false; + else + return "on or off expected"; + + return 0; +} + +void +BooleanValueSetting::SaveSettingValue(Settings *settings) +{ + settings->Write(fValue ? "on" : "off"); +} + diff --git a/src/kits/tracker/Settings.h b/src/kits/tracker/Settings.h new file mode 100644 index 0000000000..3fc385796a --- /dev/null +++ b/src/kits/tracker/Settings.h @@ -0,0 +1,138 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef _SETTINGS_H_ +#define _SETTINGS_H_ + +#include +#include "SettingsHandler.h" + +namespace BPrivate { + +extern Settings *settings; + +class StringValueSetting : public SettingsArgvDispatcher { + // simple string setting +public: + StringValueSetting(const char *name, const char *defaultValue, + const char *valueExpectedErrorString, + const char *wrongValueErrorString); + + virtual ~StringValueSetting(); + + void ValueChanged(const char *newValue); + const char *Value() const; + virtual const char *Handle(const char *const *argv); + +protected: + virtual void SaveSettingValue(Settings *); + virtual bool NeedsSaving() const; + + const char *fDefaultValue; + const char *fValueExpectedErrorString; + const char *fWrongValueErrorString; + BString fValue; +}; + +class EnumeratedStringValueSetting : public StringValueSetting { + // string setting, values that do not match string enumeration + // are rejected +public: + EnumeratedStringValueSetting(const char *name, const char *defaultValue, + const char *const *values, const char *valueExpectedErrorString, + const char *wrongValueErrorString); + + void ValueChanged(const char *newValue); + virtual const char *Handle(const char *const *argv); + +protected: + const char *const *fValues; +}; + +class ScalarValueSetting : public SettingsArgvDispatcher { + // simple int32 setting +public: + ScalarValueSetting(const char *name, int32 defaultValue, + const char *valueExpectedErrorString, const char *wrongValueErrorString, + int32 min = LONG_MIN, int32 max = LONG_MAX); + + void ValueChanged(int32 newValue); + int32 Value() const; + void GetValueAsString(char *) const; + virtual const char *Handle(const char *const *argv); + +protected: + virtual void SaveSettingValue(Settings *); + virtual bool NeedsSaving() const; + + int32 fDefaultValue; + int32 fValue; + int32 fMax; + int32 fMin; + + const char *fValueExpectedErrorString; + const char *fWrongValueErrorString; +}; + +class HexScalarValueSetting : public ScalarValueSetting { + // hexadecimal int32 setting +public: + HexScalarValueSetting(const char *name, int32 defaultValue, + const char *valueExpectedErrorString, const char *wrongValueErrorString, + int32 min = LONG_MIN, int32 max = LONG_MAX); + + void GetValueAsString(char *buffer) const; + +protected: + virtual void SaveSettingValue(Settings *settings); +}; + +class BooleanValueSetting : public ScalarValueSetting { + // on-off setting +public: + BooleanValueSetting(const char *name, bool defaultValue); + + bool Value() const; + void SetValue(bool value); + virtual const char *Handle(const char *const *argv); + +protected: + virtual void SaveSettingValue(Settings *); +}; + +} + +using namespace BPrivate; + +#endif /* _SETTINGS_H_ */ diff --git a/src/kits/tracker/SettingsHandler.cpp b/src/kits/tracker/SettingsHandler.cpp new file mode 100644 index 0000000000..58fea7533c --- /dev/null +++ b/src/kits/tracker/SettingsHandler.cpp @@ -0,0 +1,444 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + + +#include "SettingsHandler.h" + +ArgvParser::ArgvParser(const char *name) + : fFile(0), + fBuffer(NULL), + fPos(-1), + fArgc(0), + fCurrentArgv(0), + fCurrentArgsPos(-1), + fSawBackslash(false), + fEatComment(false), + fInDoubleQuote(false), + fInSingleQuote(false), + fLineNo(0), + fFileName(name) +{ + fFile = fopen(fFileName, "r"); + if (!fFile) { + PRINT(("Error opening %s\n", fFileName)); + return; + } + fBuffer = new char [kBufferSize]; + fCurrentArgv = new char * [1024]; +} + + +ArgvParser::~ArgvParser() +{ + delete [] fBuffer; + + MakeArgvEmpty(); + delete [] fCurrentArgv; + + if (fFile) + fclose(fFile); +} + +void +ArgvParser::MakeArgvEmpty() +{ + // done with current argv, free it up + for (int32 index = 0; index < fArgc; index++) + delete[] fCurrentArgv[index]; + + fArgc = 0; +} + +status_t +ArgvParser::SendArgv(ArgvHandler argvHandlerFunc, void *passThru) +{ + if (fArgc) { + NextArgv(); + fCurrentArgv[fArgc] = 0; + const char *result = (argvHandlerFunc)(fArgc, fCurrentArgv, passThru); + if (result) + printf("File %s; Line %ld # %s", fFileName, fLineNo, result); + MakeArgvEmpty(); + if (result) + return B_ERROR; + } + + return B_OK; +} + +void +ArgvParser::NextArgv() +{ + if (fSawBackslash) { + fCurrentArgs[++fCurrentArgsPos] = '\\'; + fSawBackslash = false; + } + fCurrentArgs[++fCurrentArgsPos] = '\0'; + // terminate current arg pos + + // copy it as a string to the current argv slot + fCurrentArgv[fArgc] = new char [strlen(fCurrentArgs) + 1]; + strcpy(fCurrentArgv[fArgc], fCurrentArgs); + fCurrentArgsPos = -1; + fArgc++; +} + +void +ArgvParser::NextArgvIfNotEmpty() +{ + if (!fSawBackslash && fCurrentArgsPos < 0) + return; + + NextArgv(); +} + +char +ArgvParser::GetCh() +{ + if (fPos < 0 || fBuffer[fPos] == 0) { + if (fFile == 0) + return EOF; + if (fgets(fBuffer, kBufferSize, fFile) == 0) + return EOF; + fPos = 0; + } + return fBuffer[fPos++]; +} + +status_t +ArgvParser::EachArgv(const char *name, ArgvHandler argvHandlerFunc, void *passThru) +{ + ArgvParser parser(name); + return parser.EachArgvPrivate(name, argvHandlerFunc, passThru); +} + +status_t +ArgvParser::EachArgvPrivate(const char *name, ArgvHandler argvHandlerFunc, void *passThru) +{ + status_t result; + + for (;;) { + char ch = GetCh(); + if (ch == EOF) { + // done with fFile + if (fInDoubleQuote || fInSingleQuote) { + printf("File %s # unterminated quote at end of file\n", name); + result = B_ERROR; + break; + } + result = SendArgv(argvHandlerFunc, passThru); + break; + } + + if (ch == '\n' || ch == '\r') { + // handle new line + fEatComment = false; + if (!fSawBackslash && (fInDoubleQuote || fInSingleQuote)) { + printf("File %s ; Line %ld # unterminated quote\n", name, fLineNo); + result = B_ERROR; + break; + } + fLineNo++; + if (fSawBackslash) { + fSawBackslash = false; + continue; + } + // end of line, flush all argv + result = SendArgv(argvHandlerFunc, passThru); + if (result != B_OK) + break; + + continue; + } + + if (fEatComment) + continue; + + if (!fSawBackslash) { + if (!fInDoubleQuote && !fInSingleQuote) { + if (ch == ';') { + // semicolon is a command separator, pass on the whole argv + result = SendArgv(argvHandlerFunc, passThru); + if (result != B_OK) + break; + continue; + } else if (ch == '#') { + // ignore everything on this line after this character + fEatComment = true; + continue; + } else if (ch == ' ' || ch == '\t') { + // space or tab separates the individual arg strings + NextArgvIfNotEmpty(); + continue; + } else if (!fSawBackslash && ch == '\\') { + // the next character is escaped + fSawBackslash = true; + continue; + } + } + if (!fInSingleQuote && ch == '"') { + // enter/exit double quote handling + fInDoubleQuote = !fInDoubleQuote; + continue; + } + if (!fInDoubleQuote && ch == '\'') { + // enter/exit single quote handling + fInSingleQuote = !fInSingleQuote; + continue; + } + } else { + // we just pass through the escape sequence as is + fCurrentArgs[++fCurrentArgsPos] = '\\'; + fSawBackslash = false; + } + fCurrentArgs[++fCurrentArgsPos] = ch; + } + + return result; +} + + +SettingsArgvDispatcher::SettingsArgvDispatcher(const char *name) + : name(name) +{ +} + +void +SettingsArgvDispatcher::SaveSettings(Settings *settings, bool onlyIfNonDefault) +{ + if (!onlyIfNonDefault || NeedsSaving()) { + settings->Write("%s ", Name()); + SaveSettingValue(settings); + settings->Write("\n"); + } +} + +bool +SettingsArgvDispatcher::HandleRectValue(BRect &result, const char *const *argv, + bool printError) +{ + if (!*argv) { + if (printError) + printf("rect left expected"); + return false; + } + result.left = atoi(*argv); + if (!*++argv) { + if (printError) + printf("rect top expected"); + return false; + } + result.top = atoi(*argv); + if (!*++argv) { + if (printError) + printf("rect right expected"); + return false; + } + result.right = atoi(*argv); + if (!*++argv) { + if (printError) + printf("rect bottom expected"); + return false; + } + result.bottom = atoi(*argv); + return true; +} + +void +SettingsArgvDispatcher::WriteRectValue(Settings *setting, BRect rect) +{ + setting->Write("%d %d %d %d", (int32)rect.left, (int32)rect.top, + (int32)rect.right, (int32)rect.bottom); +} + +Settings::Settings(const char *filename, const char *settingsDirName) + : fFileName(filename), + fSettingsDir(settingsDirName), + fList(0), + fCount(0), + fListSize(30), + fCurrentSettings(0) +{ + fList = (SettingsArgvDispatcher **)calloc((size_t)fListSize, sizeof(SettingsArgvDispatcher *)); +} + + +Settings::~Settings() +{ + for (int32 index = 0; index < fCount; index++) + delete fList[index]; + + free(fList); +} + + +const char * +Settings::ParseUserSettings(int, const char *const *argv, void *castToThis) +{ + if (!*argv) + return 0; + + SettingsArgvDispatcher *handler = ((Settings *)castToThis)->Find(*argv); + if (!handler) + return "unknown command"; + return handler->Handle(argv); +} + +bool +Settings::Add(SettingsArgvDispatcher *setting) +{ + // check for uniqueness + if (Find(setting->Name())) + return false; + + if (fCount >= fListSize) { + fListSize += 30; + fList = (SettingsArgvDispatcher **)realloc(fList, + fListSize * sizeof(SettingsArgvDispatcher *)); + } + fList[fCount++] = setting; + return true; +} + +SettingsArgvDispatcher * +Settings::Find(const char *name) +{ + for (int32 index = 0; index < fCount; index++) + if (strcmp(name, fList[index]->Name()) == 0) + return fList[index]; + + return NULL; +} + +void +Settings::TryReadingSettings() +{ + BPath prefsPath; + if (find_directory(B_USER_SETTINGS_DIRECTORY, &prefsPath, true) == B_OK) { + prefsPath.Append(fSettingsDir); + + BPath path(prefsPath); + path.Append(fFileName); + ArgvParser::EachArgv(path.Path(), Settings::ParseUserSettings, this); + } +} + +void +Settings::SaveSettings(bool onlyIfNonDefault) +{ + SaveCurrentSettings(onlyIfNonDefault); +} + +void +Settings::MakeSettingsDirectory(BDirectory *resultingSettingsDir) +{ + BPath path; + if (find_directory(B_USER_SETTINGS_DIRECTORY, &path, true) != B_OK) + return; + + // make sure there is a directory + // mkdir() will only make one leaf at a time, unfortunately + path.Append(fSettingsDir); + char * ptr = (char *)alloca(strlen(path.Path()) + 1); + strcpy(ptr, path.Path()); + char * end = ptr+strlen(ptr); + char * mid = ptr+1; + while (mid < end) { + mid = strchr(mid, '/'); + if (!mid) break; + *mid = 0; + mkdir(ptr, 0777); + *mid = '/'; + mid++; + } + mkdir(ptr, 0777); + resultingSettingsDir->SetTo(path.Path()); +} + +void +Settings::SaveCurrentSettings(bool onlyIfNonDefault) +{ + BDirectory settingsDir; + MakeSettingsDirectory(&settingsDir); + + if (settingsDir.InitCheck() != B_OK) + return; + + // nuke old settings + BEntry entry(&settingsDir, fFileName); + entry.Remove(); + + BFile prefs(&entry, O_RDWR | O_CREAT); + if (prefs.InitCheck() != B_OK) + return; + + fCurrentSettings = &prefs; + for (int32 index = 0; index < fCount; index++) + fList[index]->SaveSettings(this, onlyIfNonDefault); + + fCurrentSettings = NULL; +} + +void +Settings::Write(const char *format, ...) +{ + va_list args; + + va_start(args, format); + VSWrite(format, args); + va_end(args); +} + +void +Settings::VSWrite(const char *format, va_list arg) +{ + char fBuffer[2048]; + vsprintf(fBuffer, format, arg); + ASSERT(fCurrentSettings && fCurrentSettings->InitCheck() == B_OK); + fCurrentSettings->Write(fBuffer, strlen(fBuffer)); +} diff --git a/src/kits/tracker/SettingsHandler.h b/src/kits/tracker/SettingsHandler.h new file mode 100644 index 0000000000..7fc23e3221 --- /dev/null +++ b/src/kits/tracker/SettingsHandler.h @@ -0,0 +1,172 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef __SETTINGS_FILE__ +#define __SETTINGS_FILE__ + +#include +#include +#include +#include +#include + +class BFile; +class BDirectory; +class BRect; + +namespace BPrivate { + +class Settings; + +typedef const char *(*ArgvHandler)(int argc, const char *const *argv, void *params); + // return 0 or error string if parsing failed + + +const int32 kBufferSize = 1024; + +class ArgvParser { + // this class opens a text file and passes the context in argv + // format to a specified handler +public: + static status_t EachArgv(const char *name, + ArgvHandler argvHandlerFunc, void *passThru); + +private: + ArgvParser(const char *name); + ~ArgvParser(); + + status_t EachArgvPrivate(const char *name, + ArgvHandler argvHandlerFunc, void *passThru); + char GetCh(); + + status_t SendArgv(ArgvHandler argvHandlerFunc, void *passThru); + // done with a whole line of argv, send it off and get ready + // to build a new one + + void NextArgv(); + // done with current string, get ready to start building next + void NextArgvIfNotEmpty(); + // as above, don't commint current string if empty + + void MakeArgvEmpty(); + + FILE *fFile; + char *fBuffer; + int32 fPos; + + int fArgc; + char **fCurrentArgv; + + int32 fCurrentArgsPos; + char fCurrentArgs [1024]; + + bool fSawBackslash; + bool fEatComment; + bool fInDoubleQuote; + bool fInSingleQuote; + + int32 fLineNo; + const char *fFileName; +}; + +class SettingsArgvDispatcher { + // base class for a single setting item +public: + SettingsArgvDispatcher(const char *name); + + void SaveSettings(Settings *settings, bool onlyIfNonDefault); + + const char *Name() const + { return name; } + // name as it appears in the settings file + + virtual const char *Handle(const char *const *argv) = 0; + // override this adding an argv parser that reads in the + // values in argv format for this setting + // return a pointer to an error message or null if parsed OK + + + // some handy reader/writer calls + bool HandleRectValue(BRect &, const char *const *argv, bool printError = true); + void WriteRectValue(Settings *, BRect); + +protected: + virtual void SaveSettingValue(Settings *settings) = 0; + // override this to save the current value of this setting in a + // text format + + virtual bool NeedsSaving() const + { return true; } + // override to return false if current value is equal to the default + // and does not need saving +private: + const char *name; +}; + +class Settings { + // this class is a list of all the settings handlers, reads and + // saves the settings file +public: + Settings(const char *filename, const char *settingsDirName); + ~Settings(); + void TryReadingSettings(); + void SaveSettings(bool onlyIfNonDefault = true); + + bool Add(SettingsArgvDispatcher *); + // return false if argv dispatcher with the same name already + // registered + + void Write(const char *format, ...); + void VSWrite(const char *, va_list); + +private: + void MakeSettingsDirectory(BDirectory *); + + SettingsArgvDispatcher *Find(const char *); + static const char *ParseUserSettings(int, const char *const *argv, void *); + void SaveCurrentSettings(bool onlyIfNonDefault); + + const char *fFileName; + const char *fSettingsDir; // currently unused + SettingsArgvDispatcher **fList; + int32 fCount; + int32 fListSize; + BFile *fCurrentSettings; +}; + +} + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/SettingsViews.cpp b/src/kits/tracker/SettingsViews.cpp new file mode 100644 index 0000000000..e4ef8ba4c1 --- /dev/null +++ b/src/kits/tracker/SettingsViews.cpp @@ -0,0 +1,1515 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include + +#include "Commands.h" +#include "DeskWindow.h" +#include "Model.h" +#include "SettingsViews.h" +#include "Tracker.h" +#include "WidgetAttributeText.h" + +#include +#include +#include +#include +#include +#include + +const uint32 kSpaceBarSwitchColor = 'SBsc'; + + +SettingsView::SettingsView(BRect rect, const char *name) + : BView(rect, name, B_FOLLOW_ALL_SIDES, 0) +{ +} + +SettingsView::~SettingsView() +{ +} + +// The inherited functions should set the default values +// and update the UI gadgets. The latter can by done by +// calling ShowCurrentSettings(). +void SettingsView::SetDefaults() {} + +// The inherited functions should set the values that was +// active when the settings window opened. It should also +// update the UI widgets accordingly, preferrable by calling +// ShowCurrentSettings(). +void SettingsView::Revert() {} + +// This function is called when the window is shown to let +// the settings views record the state to revert to. +void SettingsView::RecordRevertSettings() {} + +// This function is used by the window to tell the view +// to display the current settings in the tracker. +void SettingsView::ShowCurrentSettings(bool) {} + +// This function is used by the window to tell whether +// it can ghost the revert button or not. It it shows the +// reverted settings, this function should return true. +bool SettingsView::ShowsRevertSettings() const { return true; } + +namespace BPrivate { + const float kBorderSpacing = 5.0f; + const float kItemHeight = 18.0f; + const float kItemExtraSpacing = 2.0f; + const float kIndentSpacing = 12.0f; +} + +//------------------------------------------------------------------------ +// #pragma mark - + +DesktopSettingsView::DesktopSettingsView(BRect rect) + : SettingsView(rect, "DesktopSettingsView") +{ + BRect frame = BRect(kBorderSpacing, kBorderSpacing, rect.Width() + - 2 * kBorderSpacing, kBorderSpacing + kItemHeight); + + fShowDisksIconRadioButton = new BRadioButton(frame, "", "Show Disks Icon", + new BMessage(kShowDisksIconChanged)); + AddChild(fShowDisksIconRadioButton); + fShowDisksIconRadioButton->ResizeToPreferred(); + + const float itemSpacing = fShowDisksIconRadioButton->Bounds().Height() + kItemExtraSpacing; + + frame.OffsetBy(0, itemSpacing); + + fMountVolumesOntoDesktopRadioButton = + new BRadioButton(frame, "", "Show Volumes On Desktop", + new BMessage(kVolumesOnDesktopChanged)); + AddChild(fMountVolumesOntoDesktopRadioButton); + fMountVolumesOntoDesktopRadioButton->ResizeToPreferred(); + + frame.OffsetBy(20, itemSpacing); + + fMountSharedVolumesOntoDesktopCheckBox = + new BCheckBox(frame, "", "Show Shared Volumes On Desktop", + new BMessage(kVolumesOnDesktopChanged)); + AddChild(fMountSharedVolumesOntoDesktopCheckBox); + fMountSharedVolumesOntoDesktopCheckBox->ResizeToPreferred(); + + frame.OffsetBy(-20, 2 * itemSpacing); + + fIntegrateNonBootBeOSDesktopsCheckBox = + new BCheckBox(frame, "", "Integrate Non-Boot BeOS Desktops", + new BMessage(kDesktopIntegrationChanged)); + AddChild(fIntegrateNonBootBeOSDesktopsCheckBox); + fIntegrateNonBootBeOSDesktopsCheckBox->ResizeToPreferred(); + + frame.OffsetBy(0, itemSpacing); + + fEjectWhenUnmountingCheckBox = + new BCheckBox(frame, "", "Eject When Unmounting", + new BMessage(kEjectWhenUnmountingChanged)); + AddChild(fEjectWhenUnmountingCheckBox); + fEjectWhenUnmountingCheckBox->ResizeToPreferred(); + + frame.OffsetBy(0, itemSpacing); + + BButton *button = + new BButton(BRect(kBorderSpacing, rect.Height() - kBorderSpacing - 20, + kBorderSpacing + 100, rect.Height() - kBorderSpacing), + "", "Mount Settings"B_UTF8_ELLIPSIS, new BMessage(kRunAutomounterSettings)); + AddChild(button); + + button->ResizeToPreferred(); + button->MoveBy(0, rect.Height() - kBorderSpacing - button->Frame().bottom); + button->SetTarget(be_app); + +} + +void +DesktopSettingsView::AttachedToWindow() +{ + fShowDisksIconRadioButton->SetTarget(this); + fMountVolumesOntoDesktopRadioButton->SetTarget(this); + fMountSharedVolumesOntoDesktopCheckBox->SetTarget(this); + fIntegrateNonBootBeOSDesktopsCheckBox->SetTarget(this); + fEjectWhenUnmountingCheckBox->SetTarget(this); +} + +void +DesktopSettingsView::MessageReceived(BMessage *message) +{ + TTracker *tracker = dynamic_cast(be_app); + if (!tracker) + return; + + TrackerSettings settings; + + switch (message->what) { + case kShowDisksIconChanged: + { + // Turn on and off related settings: + fMountVolumesOntoDesktopRadioButton->SetValue( + !fShowDisksIconRadioButton->Value() == 1); + fMountSharedVolumesOntoDesktopCheckBox->SetEnabled( + fMountVolumesOntoDesktopRadioButton->Value() == 1); + + // Set the new settings in the tracker: + settings.SetShowDisksIcon(fShowDisksIconRadioButton->Value() == 1); + settings.SetMountVolumesOntoDesktop( + fMountVolumesOntoDesktopRadioButton->Value() == 1); + settings.SetMountSharedVolumesOntoDesktop( + fMountSharedVolumesOntoDesktopCheckBox->Value() == 1); + + // Construct the notification message: + BMessage notificationMessage; + notificationMessage.AddBool("ShowDisksIcon", + fShowDisksIconRadioButton->Value() == 1); + notificationMessage.AddBool("MountVolumesOntoDesktop", + fMountVolumesOntoDesktopRadioButton->Value() == 1); + notificationMessage.AddBool("MountSharedVolumesOntoDesktop", + fMountSharedVolumesOntoDesktopCheckBox->Value() == 1); + + // Send the notification message: + tracker->SendNotices(kVolumesOnDesktopChanged, ¬ificationMessage); + + // Tell the settings window the contents have changed: + Window()->PostMessage(kSettingsContentsModified); + break; + } + + case kVolumesOnDesktopChanged: + { + // Turn on and off related settings: + fShowDisksIconRadioButton->SetValue( + !fMountVolumesOntoDesktopRadioButton->Value() == 1); + fMountSharedVolumesOntoDesktopCheckBox->SetEnabled( + fMountVolumesOntoDesktopRadioButton->Value() == 1); + + // Set the new settings in the tracker: + settings.SetShowDisksIcon(fShowDisksIconRadioButton->Value() == 1); + settings.SetMountVolumesOntoDesktop( + fMountVolumesOntoDesktopRadioButton->Value() == 1); + settings.SetMountSharedVolumesOntoDesktop( + fMountSharedVolumesOntoDesktopCheckBox->Value() == 1); + + // Construct the notification message: + BMessage notificationMessage; + notificationMessage.AddBool("ShowDisksIcon", + fShowDisksIconRadioButton->Value() == 1); + notificationMessage.AddBool("MountVolumesOntoDesktop", + fMountVolumesOntoDesktopRadioButton->Value() == 1); + notificationMessage.AddBool("MountSharedVolumesOntoDesktop", + fMountSharedVolumesOntoDesktopCheckBox->Value() == 1); + + // Send the notification message: + tracker->SendNotices(kVolumesOnDesktopChanged, ¬ificationMessage); + + // Tell the settings window the contents have changed: + Window()->PostMessage(kSettingsContentsModified); + break; + } + + case kDesktopIntegrationChanged: + { + // Set the new settings in the tracker: + settings.SetIntegrateNonBootBeOSDesktops( + fIntegrateNonBootBeOSDesktopsCheckBox->Value() == 1); + + // Construct the notification message: + BMessage notificationMessage; + notificationMessage.AddBool("MountVolumesOntoDesktop", + fMountVolumesOntoDesktopRadioButton->Value() == 1); + notificationMessage.AddBool("MountSharedVolumesOntoDesktop", + fMountSharedVolumesOntoDesktopCheckBox->Value() == 1); + notificationMessage.AddBool("IntegrateNonBootBeOSDesktops", + fIntegrateNonBootBeOSDesktopsCheckBox->Value() == 1); + + // Send the notification message: + tracker->SendNotices(kDesktopIntegrationChanged, ¬ificationMessage); + + // Tell the settings window the contents have changed: + Window()->PostMessage(kSettingsContentsModified); + break; + } + + case kEjectWhenUnmountingChanged: + { + settings.SetEjectWhenUnmounting( + fEjectWhenUnmountingCheckBox->Value() == 1); + + // Construct the notification message: + BMessage notificationMessage; + notificationMessage.AddBool("EjectWhenUnmounting", + fEjectWhenUnmountingCheckBox->Value() == 1); + + // Send the notification message: + tracker->SendNotices(kEjectWhenUnmountingChanged, ¬ificationMessage); + + // Tell the settings window the contents have changed: + Window()->PostMessage(kSettingsContentsModified); + break; + } + + default: + _inherited::MessageReceived(message); + } +} + + +void +DesktopSettingsView::SetDefaults() +{ + // ToDo: Avoid the duplication of the default values. + TrackerSettings settings; + + settings.SetShowDisksIcon(false); + settings.SetMountVolumesOntoDesktop(true); + settings.SetMountSharedVolumesOntoDesktop(false); + settings.SetIntegrateNonBootBeOSDesktops(true); + settings.SetEjectWhenUnmounting(true); + + ShowCurrentSettings(true); + // true -> send notices about the change +} + + +void +DesktopSettingsView::Revert() +{ + TrackerSettings settings; + + settings.SetShowDisksIcon(fShowDisksIcon); + settings.SetMountVolumesOntoDesktop(fMountVolumesOntoDesktop); + settings.SetMountSharedVolumesOntoDesktop(fMountSharedVolumesOntoDesktop); + settings.SetIntegrateNonBootBeOSDesktops(fIntegrateNonBootBeOSDesktops); + settings.SetEjectWhenUnmounting(fEjectWhenUnmounting); + + ShowCurrentSettings(true); + // true -> send notices about the change +} + +void +DesktopSettingsView::ShowCurrentSettings(bool sendNotices) +{ + TrackerSettings settings; + + fShowDisksIconRadioButton->SetValue(settings.ShowDisksIcon()); + fMountVolumesOntoDesktopRadioButton->SetValue(settings.MountVolumesOntoDesktop()); + + fMountSharedVolumesOntoDesktopCheckBox->SetValue(settings.MountSharedVolumesOntoDesktop()); + fMountSharedVolumesOntoDesktopCheckBox->SetEnabled(settings.MountVolumesOntoDesktop()); + + fIntegrateNonBootBeOSDesktopsCheckBox->SetValue(settings.IntegrateNonBootBeOSDesktops()); + + fEjectWhenUnmountingCheckBox->SetValue(settings.EjectWhenUnmounting()); + + if (sendNotices) { + TTracker *tracker = dynamic_cast(be_app); + if (!tracker) + return; + + // Construct the notification message: + BMessage notificationMessage; + notificationMessage.AddBool("ShowDisksIcon", + fShowDisksIconRadioButton->Value() == 1); + notificationMessage.AddBool("MountVolumesOntoDesktop", + fMountVolumesOntoDesktopRadioButton->Value() == 1); + notificationMessage.AddBool("MountSharedVolumesOntoDesktop", + fMountSharedVolumesOntoDesktopCheckBox->Value() == 1); + notificationMessage.AddBool("IntegrateNonBootBeOSDesktops", + fIntegrateNonBootBeOSDesktopsCheckBox->Value() == 1); + notificationMessage.AddBool("EjectWhenUnmounting", + fEjectWhenUnmountingCheckBox->Value() == 1); + + // Send notices to the tracker about the change: + tracker->SendNotices(kVolumesOnDesktopChanged, ¬ificationMessage); + tracker->SendNotices(kDesktopIntegrationChanged, ¬ificationMessage); + } +} + + +void +DesktopSettingsView::RecordRevertSettings() +{ + TrackerSettings settings; + + fShowDisksIcon = settings.ShowDisksIcon(); + fMountVolumesOntoDesktop = settings.MountVolumesOntoDesktop(); + fMountSharedVolumesOntoDesktop = settings.MountSharedVolumesOntoDesktop(); + fIntegrateNonBootBeOSDesktops = settings.IntegrateNonBootBeOSDesktops(); + fEjectWhenUnmounting = settings.EjectWhenUnmounting(); +} + + +bool +DesktopSettingsView::ShowsRevertSettings() const +{ + return + (fShowDisksIcon == + (fShowDisksIconRadioButton->Value() > 0)) + && (fMountVolumesOntoDesktop == + (fMountVolumesOntoDesktopRadioButton->Value() > 0)) + && (fMountSharedVolumesOntoDesktop == + (fMountSharedVolumesOntoDesktopCheckBox->Value() > 0)) + && (fIntegrateNonBootBeOSDesktops == + (fIntegrateNonBootBeOSDesktopsCheckBox->Value() > 0)) + && (fEjectWhenUnmounting == + (fEjectWhenUnmountingCheckBox->Value() > 0)); +} + + +//------------------------------------------------------------------------ +// #pragma mark - + + +WindowsSettingsView::WindowsSettingsView(BRect rect) + : SettingsView(rect, "WindowsSettingsView") +{ + BRect frame = BRect(kBorderSpacing, kBorderSpacing, rect.Width() + - 2 * kBorderSpacing, kBorderSpacing + kItemHeight); + + fShowFullPathInTitleBarCheckBox = new BCheckBox(frame, "", "Show Full Path In Title Bar", + new BMessage(kWindowsShowFullPathChanged)); + AddChild(fShowFullPathInTitleBarCheckBox); + fShowFullPathInTitleBarCheckBox->ResizeToPreferred(); + + const float itemSpacing = fShowFullPathInTitleBarCheckBox->Bounds().Height() + kItemExtraSpacing; + + frame.OffsetBy(0, itemSpacing); + + fSingleWindowBrowseCheckBox = new BCheckBox(frame, "", "Single Window Browse", + new BMessage(kSingleWindowBrowseChanged)); + AddChild(fSingleWindowBrowseCheckBox); + fSingleWindowBrowseCheckBox->ResizeToPreferred(); + + frame.OffsetBy(20, itemSpacing); + + fShowNavigatorCheckBox = new BCheckBox(frame, "", "Show Navigator", + new BMessage(kShowNavigatorChanged)); + AddChild(fShowNavigatorCheckBox); + fShowNavigatorCheckBox->ResizeToPreferred(); + + frame.OffsetBy(-20, itemSpacing); + + + fShowSelectionWhenInactiveCheckBox = new BCheckBox(frame, "", "Show Selection When Inactive", + new BMessage(kShowSelectionWhenInactiveChanged)); + AddChild(fShowSelectionWhenInactiveCheckBox); + fShowSelectionWhenInactiveCheckBox->ResizeToPreferred(); + + frame.OffsetBy(0, itemSpacing); + + fTransparentSelectionCheckBox = new BCheckBox(frame, "", "Transparent Selection Box", + new BMessage(kTransparentSelectionChanged)); + AddChild(fTransparentSelectionCheckBox); + fTransparentSelectionCheckBox->ResizeToPreferred(); + + frame.OffsetBy(0, itemSpacing); + + fSortFolderNamesFirstCheckBox = new BCheckBox(frame, "", "Sort Folder Names First", + new BMessage(kSortFolderNamesFirstChanged)); + AddChild(fSortFolderNamesFirstCheckBox); + fSortFolderNamesFirstCheckBox->ResizeToPreferred(); +} + + +void +WindowsSettingsView::AttachedToWindow() +{ + fSingleWindowBrowseCheckBox->SetTarget(this); + fShowNavigatorCheckBox->SetTarget(this); + fShowFullPathInTitleBarCheckBox->SetTarget(this); + fShowSelectionWhenInactiveCheckBox->SetTarget(this); + fTransparentSelectionCheckBox->SetTarget(this); + fSortFolderNamesFirstCheckBox->SetTarget(this); +} + + +void +WindowsSettingsView::MessageReceived(BMessage *message) +{ + TTracker *tracker = dynamic_cast(be_app); + if (!tracker) + return; + TrackerSettings settings; + + switch (message->what) { + case kWindowsShowFullPathChanged: + settings.SetShowFullPathInTitleBar(fShowFullPathInTitleBarCheckBox->Value() == 1); + tracker->SendNotices(kWindowsShowFullPathChanged); + Window()->PostMessage(kSettingsContentsModified); + break; + + case kSingleWindowBrowseChanged: + settings.SetSingleWindowBrowse(fSingleWindowBrowseCheckBox->Value() == 1); + if (fSingleWindowBrowseCheckBox->Value() == 0) { + fShowNavigatorCheckBox->SetEnabled(false); + settings.SetShowNavigator(0); + } else { + fShowNavigatorCheckBox->SetEnabled(true); + settings.SetShowNavigator(fShowNavigatorCheckBox->Value() != 0); + } + tracker->SendNotices(kShowNavigatorChanged); + tracker->SendNotices(kSingleWindowBrowseChanged); + Window()->PostMessage(kSettingsContentsModified); + break; + + case kShowNavigatorChanged: + settings.SetShowNavigator(fShowNavigatorCheckBox->Value() == 1); + tracker->SendNotices(kShowNavigatorChanged); + Window()->PostMessage(kSettingsContentsModified); + break; + + case kShowSelectionWhenInactiveChanged: + { + settings.SetShowSelectionWhenInactive( + fShowSelectionWhenInactiveCheckBox->Value() == 1); + + // Make the notification message and send it to the tracker: + BMessage notificationMessage; + notificationMessage.AddBool("ShowSelectionWhenInactive", + fShowSelectionWhenInactiveCheckBox->Value() == 1); + tracker->SendNotices(kShowSelectionWhenInactiveChanged, ¬ificationMessage); + + Window()->PostMessage(kSettingsContentsModified); + break; + } + + case kTransparentSelectionChanged: + { + settings.SetTransparentSelection( + fTransparentSelectionCheckBox->Value() == 1); + + // Make the notification message and send it to the tracker: + BMessage notificationMessage; + notificationMessage.AddBool("TransparentSelection", + fTransparentSelectionCheckBox->Value() == 1); + tracker->SendNotices(kTransparentSelectionChanged, ¬ificationMessage); + + Window()->PostMessage(kSettingsContentsModified); + break; + } + + case kSortFolderNamesFirstChanged: + { + settings.SetSortFolderNamesFirst(fSortFolderNamesFirstCheckBox->Value() == 1); + + // Make the notification message and send it to the tracker: + BMessage notificationMessage; + notificationMessage.AddBool("SortFolderNamesFirst", + fSortFolderNamesFirstCheckBox->Value() == 1); + tracker->SendNotices(kSortFolderNamesFirstChanged, ¬ificationMessage); + + Window()->PostMessage(kSettingsContentsModified); + break; + } + + default: + _inherited::MessageReceived(message); + break; + } +} + + +void +WindowsSettingsView::SetDefaults() +{ + TrackerSettings settings; + + settings.SetShowFullPathInTitleBar(false); + settings.SetSingleWindowBrowse(false); + settings.SetShowNavigator(false); + settings.SetShowSelectionWhenInactive(true); + settings.SetTransparentSelection(false); + settings.SetSortFolderNamesFirst(false); + + ShowCurrentSettings(true); + // true -> send notices about the change +} + + +void +WindowsSettingsView::Revert() +{ + TrackerSettings settings; + + settings.SetShowFullPathInTitleBar(fShowFullPathInTitleBar); + settings.SetSingleWindowBrowse(fSingleWindowBrowse); + settings.SetShowNavigator(fShowNavigator); + settings.SetShowSelectionWhenInactive(fShowSelectionWhenInactive); + settings.SetTransparentSelection(fTransparentSelection); + settings.SetSortFolderNamesFirst(fSortFolderNamesFirst); + + ShowCurrentSettings(true); + // true -> send notices about the change +} + + +void +WindowsSettingsView::ShowCurrentSettings(bool sendNotices) +{ + TTracker *tracker = dynamic_cast(be_app); + if (!tracker) + return; + TrackerSettings settings; + + fShowFullPathInTitleBarCheckBox->SetValue(settings.ShowFullPathInTitleBar()); + fSingleWindowBrowseCheckBox->SetValue(settings.SingleWindowBrowse()); + fShowNavigatorCheckBox->SetEnabled(settings.SingleWindowBrowse()); + fShowNavigatorCheckBox->SetValue(settings.ShowNavigator()); + fShowSelectionWhenInactiveCheckBox->SetValue(settings.ShowSelectionWhenInactive()); + fTransparentSelectionCheckBox->SetValue(settings.TransparentSelection()); + fSortFolderNamesFirstCheckBox->SetValue(settings.SortFolderNamesFirst()); + + if (sendNotices) { + tracker->SendNotices(kSingleWindowBrowseChanged); + tracker->SendNotices(kShowNavigatorChanged); + tracker->SendNotices(kWindowsShowFullPathChanged); + tracker->SendNotices(kShowSelectionWhenInactiveChanged); + tracker->SendNotices(kTransparentSelectionChanged); + tracker->SendNotices(kSortFolderNamesFirstChanged); + } +} + + +void +WindowsSettingsView::RecordRevertSettings() +{ + TrackerSettings settings; + + fShowFullPathInTitleBar = settings.ShowFullPathInTitleBar(); + fSingleWindowBrowse = settings.SingleWindowBrowse(); + fShowNavigator = settings.ShowNavigator(); + fShowSelectionWhenInactive = settings.ShowSelectionWhenInactive(); + fTransparentSelection = settings.TransparentSelection(); + fSortFolderNamesFirst = settings.SortFolderNamesFirst(); +} + + +bool +WindowsSettingsView::ShowsRevertSettings() const +{ + return + (fShowFullPathInTitleBar == + (fShowFullPathInTitleBarCheckBox->Value() > 0)) + && (fSingleWindowBrowse == + (fSingleWindowBrowseCheckBox->Value() > 0)) + && (fShowNavigator == + (fShowNavigatorCheckBox->Value() > 0)) + && (fShowSelectionWhenInactive == + (fShowSelectionWhenInactiveCheckBox->Value() > 0)) + && (fTransparentSelection == + (fTransparentSelectionCheckBox->Value() > 0)) + && (fSortFolderNamesFirst == + (fSortFolderNamesFirstCheckBox->Value() > 0)); +} + + +//------------------------------------------------------------------------ +// #pragma mark - + + +FilePanelSettingsView::FilePanelSettingsView(BRect rect) + : SettingsView(rect, "FilePanelSettingsView") +{ + BRect frame = BRect(kBorderSpacing, kBorderSpacing, rect.Width() + - 2 * kBorderSpacing, kBorderSpacing + kItemHeight); + + fDesktopFilePanelRootCheckBox = new BCheckBox(frame, "", "File Panel Root is Desktop", + new BMessage(kDesktopFilePanelRootChanged)); + AddChild(fDesktopFilePanelRootCheckBox); + fDesktopFilePanelRootCheckBox->ResizeToPreferred(); + + const float itemSpacing = fDesktopFilePanelRootCheckBox->Bounds().Height() + kItemExtraSpacing; + + frame.OffsetBy(0, itemSpacing); + + BRect recentBoxFrame(kBorderSpacing, frame.bottom, rect.Width() - kBorderSpacing, frame.top); + + BBox *recentBox = new BBox(recentBoxFrame, "recentBox"); + recentBox->SetLabel("Recent" B_UTF8_ELLIPSIS); + + AddChild(recentBox); + + frame = recentBoxFrame.OffsetToCopy(0,0); + frame.OffsetTo(kBorderSpacing, 3 * kBorderSpacing); + + frame.right = StringWidth("##Applications###10##"); + float divider = StringWidth("Applications") + 10; + + fRecentDocumentsTextControl = new BTextControl(frame, "", "Documents", "10", + new BMessage(kFavoriteCountChanged)); + + fRecentDocumentsTextControl->SetDivider(divider); + + frame.OffsetBy(0, itemSpacing); + + fRecentFoldersTextControl = new BTextControl(frame, "", "Folders", "10", + new BMessage(kFavoriteCountChanged)); + + fRecentFoldersTextControl->SetDivider(divider); + + recentBox->AddChild(fRecentDocumentsTextControl); + recentBox->AddChild(fRecentFoldersTextControl); + + recentBox->ResizeTo(recentBox->Frame().Width(), fRecentFoldersTextControl->Frame().bottom + kBorderSpacing); + + be_app->LockLooper(); + be_app->StartWatching(this, kFavoriteCountChangedExternally); + be_app->UnlockLooper(); +} + + +FilePanelSettingsView::~FilePanelSettingsView() +{ + be_app->LockLooper(); + be_app->StopWatching(this, kFavoriteCountChangedExternally); + be_app->UnlockLooper(); +} + + +void +FilePanelSettingsView::AttachedToWindow() +{ + fDesktopFilePanelRootCheckBox->SetTarget(this); + fRecentDocumentsTextControl->SetTarget(this); + fRecentFoldersTextControl->SetTarget(this); +} + + +void +FilePanelSettingsView::MessageReceived(BMessage *message) +{ + TTracker *tracker = dynamic_cast(be_app); + if (!tracker) + return; + TrackerSettings settings; + + switch (message->what) { + case kDesktopFilePanelRootChanged: + { + settings.SetDesktopFilePanelRoot(fDesktopFilePanelRootCheckBox->Value() == 1); + + // Make the notification message and send it to the tracker: + BMessage message; + message.AddBool("DesktopFilePanelRoot", fDesktopFilePanelRootCheckBox->Value() == 1); + tracker->SendNotices(kDesktopFilePanelRootChanged, &message); + + Window()->PostMessage(kSettingsContentsModified); + } + break; + + case kFavoriteCountChanged: + { + GetAndRefreshDisplayedFigures(); + settings.SetRecentDocumentsCount(fDisplayedDocCount); + settings.SetRecentFoldersCount(fDisplayedFolderCount); + + // Make the notification message and send it to the tracker: + BMessage message; + message.AddInt32("RecentDocuments", fDisplayedDocCount); + message.AddInt32("RecentFolders", fDisplayedFolderCount); + tracker->SendNotices(kFavoriteCountChanged, &message); + + Window()->PostMessage(kSettingsContentsModified); + } + break; + + case B_OBSERVER_NOTICE_CHANGE: + { + int32 observerWhat; + if (message->FindInt32("be:observe_change_what", &observerWhat) == B_OK) { + switch (observerWhat) { + case kFavoriteCountChangedExternally: + { + int32 count; + if (message->FindInt32("RecentApplications", &count) == B_OK) { + settings.SetRecentApplicationsCount(count); + ShowCurrentSettings(); + } + + if (message->FindInt32("RecentDocuments", &count) == B_OK) { + settings.SetRecentDocumentsCount(count); + ShowCurrentSettings(); + } + + if (message->FindInt32("RecentFolders", &count) == B_OK) { + settings.SetRecentFoldersCount(count); + ShowCurrentSettings(); + } + } + break; + } + } + } + break; + + default: + _inherited::MessageReceived(message); + break; + } +} + + +void +FilePanelSettingsView::SetDefaults() +{ + TrackerSettings settings; + + settings.SetDesktopFilePanelRoot(true); + settings.SetRecentDocumentsCount(10); + settings.SetRecentFoldersCount(10); + + ShowCurrentSettings(true); + // true -> send notices about the change +} + + +void +FilePanelSettingsView::Revert() +{ + TrackerSettings settings; + + settings.SetDesktopFilePanelRoot(fDesktopFilePanelRoot); + settings.SetRecentDocumentsCount(fRecentDocuments); + settings.SetRecentFoldersCount(fRecentFolders); + + ShowCurrentSettings(true); + // true -> send notices about the change +} + + +void +FilePanelSettingsView::ShowCurrentSettings(bool sendNotices) +{ + TTracker *tracker = dynamic_cast(be_app); + if (!tracker) + return; + TrackerSettings settings; + + fDesktopFilePanelRootCheckBox->SetValue(settings.DesktopFilePanelRoot()); + + int32 recentApplications, recentDocuments, recentFolders; + settings.RecentCounts(&recentApplications, &recentDocuments, &recentFolders); + + BString docCountText; + docCountText << recentDocuments; + fRecentDocumentsTextControl->SetText(docCountText.String()); + + BString folderCountText; + folderCountText << recentFolders; + fRecentFoldersTextControl->SetText(folderCountText.String()); + + if (sendNotices) { + // Make the notification message and send it to the tracker: + + BMessage message; + message.AddBool("DesktopFilePanelRoot", fDesktopFilePanelRootCheckBox->Value() == 1); + tracker->SendNotices(kDesktopFilePanelRootChanged, &message); + + message.AddInt32("RecentDocuments", recentDocuments); + message.AddInt32("RecentFolders", recentFolders); + tracker->SendNotices(kFavoriteCountChanged, &message); + } +} + + +void +FilePanelSettingsView::RecordRevertSettings() +{ + TrackerSettings settings; + + fDesktopFilePanelRoot = settings.DesktopFilePanelRoot(); + settings.RecentCounts(&fRecentApplications, &fRecentDocuments, &fRecentFolders); +} + + +bool +FilePanelSettingsView::ShowsRevertSettings() const +{ + GetAndRefreshDisplayedFigures(); + + return + (fDesktopFilePanelRoot == (fDesktopFilePanelRootCheckBox->Value() > 0)) + && (fDisplayedDocCount == fRecentDocuments) + && (fDisplayedFolderCount == fRecentFolders); +} + + +void +FilePanelSettingsView::GetAndRefreshDisplayedFigures() const +{ + sscanf(fRecentDocumentsTextControl->Text(), "%ld", &fDisplayedDocCount); + sscanf(fRecentFoldersTextControl->Text(), "%ld", &fDisplayedFolderCount); + + BString docCountText; + docCountText << fDisplayedDocCount; + fRecentDocumentsTextControl->SetText(docCountText.String()); + + BString folderCountText; + folderCountText << fDisplayedFolderCount; + fRecentFoldersTextControl->SetText(folderCountText.String()); +} + + +//------------------------------------------------------------------------ +// #pragma mark - + + +TimeFormatSettingsView::TimeFormatSettingsView(BRect rect) + : SettingsView(rect, "WindowsSettingsView") +{ + BRect clockBoxFrame = BRect(kBorderSpacing, kBorderSpacing, + rect.Width() / 2 - 4 * kBorderSpacing, kBorderSpacing + 5 * kItemHeight); + + BBox *clockBox = new BBox(clockBoxFrame, "Clock"); + clockBox->SetLabel("Clock"); + + AddChild(clockBox); + + BRect frame = BRect(kBorderSpacing, 2.5f*kBorderSpacing, + clockBoxFrame.Width() - 2 * kBorderSpacing, kBorderSpacing + kItemHeight); + + f24HrRadioButton = new BRadioButton(frame, "", "24 Hour", + new BMessage(kSettingsContentsModified)); + clockBox->AddChild(f24HrRadioButton); + f24HrRadioButton->ResizeToPreferred(); + + const float itemSpacing = f24HrRadioButton->Bounds().Height() + kItemExtraSpacing; + + frame.OffsetBy(0, itemSpacing); + + f12HrRadioButton = new BRadioButton(frame, "", "12 Hour", + new BMessage(kSettingsContentsModified)); + clockBox->AddChild(f12HrRadioButton); + f12HrRadioButton->ResizeToPreferred(); + + clockBox->ResizeTo(clockBox->Bounds().Width(), f12HrRadioButton->Frame().bottom + kBorderSpacing); + + + BRect dateFormatBoxFrame = BRect(clockBoxFrame.right + kBorderSpacing, kBorderSpacing, + rect.right - kBorderSpacing, kBorderSpacing + 5 * itemSpacing); + + BBox *dateFormatBox = new BBox(dateFormatBoxFrame, "Date Order"); + dateFormatBox->SetLabel("Date Order"); + + AddChild(dateFormatBox); + + frame = BRect(kBorderSpacing, 2.5f*kBorderSpacing, + dateFormatBoxFrame.Width() - 2 * kBorderSpacing, kBorderSpacing + kItemHeight); + + fYMDRadioButton = new BRadioButton(frame, "", "Year-Month-Day", + new BMessage(kSettingsContentsModified)); + dateFormatBox->AddChild(fYMDRadioButton); + fYMDRadioButton->ResizeToPreferred(); + + frame.OffsetBy(0, itemSpacing); + + fDMYRadioButton = new BRadioButton(frame, "", "Day-Month-Year", + new BMessage(kSettingsContentsModified)); + dateFormatBox->AddChild(fDMYRadioButton); + fDMYRadioButton->ResizeToPreferred(); + + frame.OffsetBy(0, itemSpacing); + + fMDYRadioButton = new BRadioButton(frame, "", "Month-Day-Year", + new BMessage(kSettingsContentsModified)); + dateFormatBox->AddChild(fMDYRadioButton); + fMDYRadioButton->ResizeToPreferred(); + + dateFormatBox->ResizeTo(dateFormatBox->Bounds().Width(), fMDYRadioButton->Frame().bottom + kBorderSpacing); + + BPopUpMenu *menu = new BPopUpMenu("Separator"); + + menu->AddItem(new BMenuItem("None", new BMessage(kSettingsContentsModified))); + menu->AddItem(new BMenuItem("Space", new BMessage(kSettingsContentsModified))); + menu->AddItem(new BMenuItem("-", new BMessage(kSettingsContentsModified))); + menu->AddItem(new BMenuItem("/", new BMessage(kSettingsContentsModified))); + menu->AddItem(new BMenuItem("\\", new BMessage(kSettingsContentsModified))); + menu->AddItem(new BMenuItem(".", new BMessage(kSettingsContentsModified))); + + frame = BRect(clockBox->Frame().left, dateFormatBox->Frame().bottom + kBorderSpacing, + rect.right - kBorderSpacing, dateFormatBox->Frame().bottom + kBorderSpacing + itemSpacing); + + fSeparatorMenuField = new BMenuField(frame, "Separator", "Separator", menu); + fSeparatorMenuField->ResizeToPreferred(); + AddChild(fSeparatorMenuField); + + frame.OffsetBy(0, 30.0f); + + BStringView *exampleView = new BStringView(frame, "", "Examples:"); + AddChild(exampleView); + exampleView->ResizeToPreferred(); + + frame.OffsetBy(0, itemSpacing); + + fLongDateExampleView = new BStringView(frame, "", ""); + AddChild(fLongDateExampleView); + fLongDateExampleView->ResizeToPreferred(); + + frame.OffsetBy(0, itemSpacing); + + fShortDateExampleView = new BStringView(frame, "", ""); + AddChild(fShortDateExampleView); + fShortDateExampleView->ResizeToPreferred(); + + UpdateExamples(); +} + + +void +TimeFormatSettingsView::AttachedToWindow() +{ + f24HrRadioButton->SetTarget(this); + f12HrRadioButton->SetTarget(this); + fYMDRadioButton->SetTarget(this); + fDMYRadioButton->SetTarget(this); + fMDYRadioButton->SetTarget(this); + + fSeparatorMenuField->Menu()->SetTargetForItems(this); +} + + +void +TimeFormatSettingsView::MessageReceived(BMessage *message) +{ + TTracker *tracker = dynamic_cast(be_app); + if (!tracker) + return; + TrackerSettings settings; + + switch (message->what) { + case kSettingsContentsModified: + { + int32 separator = 0; + BMenuItem *item = fSeparatorMenuField->Menu()->FindMarked(); + if (item) { + separator = fSeparatorMenuField->Menu()->IndexOf(item); + if (separator >= 0) + settings.SetTimeFormatSeparator((FormatSeparator)separator); + } + + DateOrder format = + fYMDRadioButton->Value() ? kYMDFormat : + fDMYRadioButton->Value() ? kDMYFormat : kMDYFormat; + + settings.SetDateOrderFormat(format); + settings.SetClockTo24Hr(f24HrRadioButton->Value() == 1); + + // Make the notification message and send it to the tracker: + BMessage notificationMessage; + notificationMessage.AddInt32("TimeFormatSeparator", separator); + notificationMessage.AddInt32("DateOrderFormat", format); + notificationMessage.AddBool("24HrClock", f24HrRadioButton->Value() == 1); + tracker->SendNotices(kDateFormatChanged, ¬ificationMessage); + + UpdateExamples(); + + Window()->PostMessage(kSettingsContentsModified); + break; + } + + default: + _inherited::MessageReceived(message); + } +} + + +void +TimeFormatSettingsView::SetDefaults() +{ + TrackerSettings settings; + + settings.SetTimeFormatSeparator(kSlashSeparator); + settings.SetDateOrderFormat(kMDYFormat); + settings.SetClockTo24Hr(false); + + ShowCurrentSettings(true); + // true -> send notices about the change +} + + +void +TimeFormatSettingsView::Revert() +{ + TrackerSettings settings; + + settings.SetTimeFormatSeparator(fSeparator); + settings.SetDateOrderFormat(fFormat); + settings.SetClockTo24Hr(f24HrClock); + + ShowCurrentSettings(true); + // true -> send notices about the change +} + + +void +TimeFormatSettingsView::ShowCurrentSettings(bool sendNotices) +{ + TrackerSettings settings; + + f24HrRadioButton->SetValue(settings.ClockIs24Hr()); + f12HrRadioButton->SetValue(!settings.ClockIs24Hr()); + + switch (settings.DateOrderFormat()) { + case kYMDFormat: + fYMDRadioButton->SetValue(1); + break; + + case kMDYFormat: + fMDYRadioButton->SetValue(1); + break; + + default: + case kDMYFormat: + fDMYRadioButton->SetValue(1); + break; + } + + FormatSeparator separator = settings.TimeFormatSeparator(); + + if (separator >= kNoSeparator && separator < kSeparatorsEnd) + fSeparatorMenuField->Menu()->ItemAt((int32)separator)->SetMarked(true); + + UpdateExamples(); + + if (sendNotices) { + TTracker *tracker = dynamic_cast(be_app); + if (!tracker) + return; + + // Make the notification message and send it to the tracker: + BMessage notificationMessage; + notificationMessage.AddInt32("TimeFormatSeparator", (int32)settings.TimeFormatSeparator()); + notificationMessage.AddInt32("DateOrderFormat", (int32)settings.DateOrderFormat()); + notificationMessage.AddBool("24HrClock", settings.ClockIs24Hr()); + tracker->SendNotices(kDateFormatChanged, ¬ificationMessage); + } +} + + +void +TimeFormatSettingsView::RecordRevertSettings() +{ + TrackerSettings settings; + + f24HrClock = settings.ClockIs24Hr(); + fSeparator = settings.TimeFormatSeparator(); + fFormat = settings.DateOrderFormat(); +} + + +bool +TimeFormatSettingsView::ShowsRevertSettings() const +{ + FormatSeparator separator; + + BMenuItem *item = fSeparatorMenuField->Menu()->FindMarked(); + if (item) { + int32 index = fSeparatorMenuField->Menu()->IndexOf(item); + if (index >= 0) + separator = (FormatSeparator)index; + else + return false; + } else + return false; + + DateOrder format = + fYMDRadioButton->Value() ? kYMDFormat : + (fDMYRadioButton->Value() ? kDMYFormat : kMDYFormat); + + return + f24HrClock == (f24HrRadioButton->Value() > 0) + && separator == fSeparator + && format == fFormat; +} + + +void +TimeFormatSettingsView::UpdateExamples() +{ + time_t timeValue = (time_t)time(NULL); + tm timeData; + localtime_r(&timeValue, &timeData); + BString timeFormat = "Internal Error!"; + char buffer[256]; + + FormatSeparator separator; + + BMenuItem *item = fSeparatorMenuField->Menu()->FindMarked(); + if (item) { + int32 index = fSeparatorMenuField->Menu()->IndexOf(item); + if (index >= 0) + separator = (FormatSeparator)index; + else + separator = kSlashSeparator; + } else + separator = kSlashSeparator; + + DateOrder order = + fYMDRadioButton->Value() ? kYMDFormat : + (fDMYRadioButton->Value() ? kDMYFormat : kMDYFormat); + + bool clockIs24hr = (f24HrRadioButton->Value() > 0); + + TimeFormat(timeFormat, 0, separator, order, clockIs24hr); + strftime(buffer, 256, timeFormat.String(), &timeData); + + fLongDateExampleView->SetText(buffer); + fLongDateExampleView->ResizeToPreferred(); + + TimeFormat(timeFormat, 4, separator, order, clockIs24hr); + strftime(buffer, 256, timeFormat.String(), &timeData); + + fShortDateExampleView->SetText(buffer); + fShortDateExampleView->ResizeToPreferred(); +} + + +//------------------------------------------------------------------------ +// #pragma mark - + + +SpaceBarSettingsView::SpaceBarSettingsView(BRect rect) + : SettingsView(rect, "SpaceBarSettingsView") +{ + BRect frame = BRect(kBorderSpacing, kBorderSpacing, rect.Width() + - 2 * kBorderSpacing, kBorderSpacing + kItemHeight); + + fSpaceBarShowCheckBox = new BCheckBox(frame, "", "Show Space Bars On Volumes", + new BMessage(kUpdateVolumeSpaceBar)); + AddChild(fSpaceBarShowCheckBox); + fSpaceBarShowCheckBox->ResizeToPreferred(); + float itemSpacing = fSpaceBarShowCheckBox->Bounds().Height() + kItemExtraSpacing; + frame.OffsetBy(0, itemSpacing); + + BPopUpMenu *menu = new BPopUpMenu(B_EMPTY_STRING); + menu->SetFont(be_plain_font); + + BMenuItem *item; + menu->AddItem(item = new BMenuItem("Used Space Color", new BMessage(kSpaceBarSwitchColor))); + item->SetMarked(true); + fCurrentColor = 0; + menu->AddItem(new BMenuItem("Free Space Color", new BMessage(kSpaceBarSwitchColor))); + menu->AddItem(new BMenuItem("Warning Space Color", new BMessage(kSpaceBarSwitchColor))); + + BBox *box = new BBox(frame); + box->SetLabel(fColorPicker = new BMenuField(frame,NULL,NULL,menu)); + AddChild(box); + + fColorControl = new BColorControl( + BPoint(8,fColorPicker->Bounds().Height() + 8 + kItemExtraSpacing), + B_CELLS_16x16,1,"SpaceColorControl",new BMessage(kSpaceBarColorChanged)); + fColorControl->SetValue(TrackerSettings().UsedSpaceColor()); + fColorControl->ResizeToPreferred(); + box->AddChild(fColorControl); + box->ResizeTo(fColorControl->Bounds().Width() + 16,fColorControl->Frame().bottom + 8); +} + + +SpaceBarSettingsView::~SpaceBarSettingsView() +{ +} + + +void +SpaceBarSettingsView::AttachedToWindow() +{ + fSpaceBarShowCheckBox->SetTarget(this); + fColorControl->SetTarget(this); + fColorPicker->Menu()->SetTargetForItems(this); +} + + +void +SpaceBarSettingsView::MessageReceived(BMessage *message) +{ + TTracker *tracker = dynamic_cast(be_app); + if (!tracker) + return; + TrackerSettings settings; + + switch (message->what) { + case kUpdateVolumeSpaceBar: + { + settings.SetShowVolumeSpaceBar(fSpaceBarShowCheckBox->Value() == 1); + Window()->PostMessage(kSettingsContentsModified); + BMessage notificationMessage; + notificationMessage.AddBool("ShowVolumeSpaceBar", settings.ShowVolumeSpaceBar()); + tracker->SendNotices(kShowVolumeSpaceBar, ¬ificationMessage); + break; + } + + case kSpaceBarSwitchColor: + { + fCurrentColor = message->FindInt32("index"); + switch (fCurrentColor) { + case 0: + fColorControl->SetValue(settings.UsedSpaceColor()); + break; + case 1: + fColorControl->SetValue(settings.FreeSpaceColor()); + break; + case 2: + fColorControl->SetValue(settings.WarningSpaceColor()); + break; + } + break; + } + case kSpaceBarColorChanged: + { + switch (fCurrentColor) { + case 0: + settings.SetUsedSpaceColor(fColorControl->ValueAsColor()); + break; + case 1: + settings.SetFreeSpaceColor(fColorControl->ValueAsColor()); + break; + case 2: + settings.SetWarningSpaceColor(fColorControl->ValueAsColor()); + break; + } + + Window()->PostMessage(kSettingsContentsModified); + BMessage notificationMessage; + tracker->SendNotices(kSpaceBarColorChanged, ¬ificationMessage); + break; + } + + default: + _inherited::MessageReceived(message); + break; + } +} + + +void +SpaceBarSettingsView::SetDefaults() +{ + TrackerSettings settings; + + settings.SetShowVolumeSpaceBar(false); + + settings.SetUsedSpaceColor(Color(0,0xcb,0,192)); + settings.SetFreeSpaceColor(Color(0xff,0xff,0xff,192)); + settings.SetWarningSpaceColor(Color(0xcb,0,0,192)); + + ShowCurrentSettings(true); + // true -> send notices about the change +} + + +void +SpaceBarSettingsView::Revert() +{ + TrackerSettings settings; + + settings.SetShowVolumeSpaceBar(fSpaceBarShow); + settings.SetUsedSpaceColor(fUsedSpaceColor); + settings.SetFreeSpaceColor(fFreeSpaceColor); + settings.SetWarningSpaceColor(fWarningSpaceColor); + + ShowCurrentSettings(true); + // true -> send notices about the change +} + + +void +SpaceBarSettingsView::ShowCurrentSettings(bool sendNotices) +{ + TTracker *tracker = dynamic_cast(be_app); + if (!tracker) + return; + TrackerSettings settings; + + fSpaceBarShowCheckBox->SetValue(settings.ShowVolumeSpaceBar()); + + switch (fCurrentColor) { + case 0: + fColorControl->SetValue(settings.UsedSpaceColor()); + break; + case 1: + fColorControl->SetValue(settings.FreeSpaceColor()); + break; + case 2: + fColorControl->SetValue(settings.WarningSpaceColor()); + break; + } + + if (sendNotices) { + BMessage notificationMessage; + notificationMessage.AddBool("ShowVolumeSpaceBar", settings.ShowVolumeSpaceBar()); + tracker->SendNotices(kShowVolumeSpaceBar, ¬ificationMessage); + + Window()->PostMessage(kSettingsContentsModified); + BMessage notificationMessage2; + tracker->SendNotices(kSpaceBarColorChanged, ¬ificationMessage2); + } +} + + +void +SpaceBarSettingsView::RecordRevertSettings() +{ + TrackerSettings settings; + + fSpaceBarShow = settings.ShowVolumeSpaceBar(); + fUsedSpaceColor = settings.UsedSpaceColor(); + fFreeSpaceColor = settings.FreeSpaceColor(); + fWarningSpaceColor = settings.WarningSpaceColor(); +} + + +bool +SpaceBarSettingsView::ShowsRevertSettings() const +{ + return (fSpaceBarShow == (fSpaceBarShowCheckBox->Value() == 1)); +} + + +//------------------------------------------------------------------------ +// #pragma mark - + + +TrashSettingsView::TrashSettingsView(BRect rect) + : SettingsView(rect, "TrashSettingsView") +{ + BRect frame = BRect(kBorderSpacing, kBorderSpacing, rect.Width() + - 2 * kBorderSpacing, kBorderSpacing + kItemHeight); + + fDontMoveFilesToTrashCheckBox = new BCheckBox(frame, "", "Don't Move Files To Trash", + new BMessage(kDontMoveFilesToTrashChanged)); + AddChild(fDontMoveFilesToTrashCheckBox); + fDontMoveFilesToTrashCheckBox->ResizeToPreferred(); + + frame.OffsetBy(0, fDontMoveFilesToTrashCheckBox->Bounds().Height() + kItemExtraSpacing); + + fAskBeforeDeleteFileCheckBox = new BCheckBox(frame, "", "Ask Before Delete", + new BMessage(kAskBeforeDeleteFileChanged)); + AddChild(fAskBeforeDeleteFileCheckBox); + fAskBeforeDeleteFileCheckBox->ResizeToPreferred(); +} + + +void +TrashSettingsView::AttachedToWindow() +{ + fDontMoveFilesToTrashCheckBox->SetTarget(this); + fAskBeforeDeleteFileCheckBox->SetTarget(this); +} + + +void +TrashSettingsView::MessageReceived(BMessage *message) +{ + TTracker *tracker = dynamic_cast(be_app); + if (!tracker) + return; + TrackerSettings settings; + + switch (message->what) { + case kDontMoveFilesToTrashChanged: + settings.SetDontMoveFilesToTrash(fDontMoveFilesToTrashCheckBox->Value() == 1); + + tracker->SendNotices(kDontMoveFilesToTrashChanged); + Window()->PostMessage(kSettingsContentsModified); + break; + + case kAskBeforeDeleteFileChanged: + settings.SetAskBeforeDeleteFile(fAskBeforeDeleteFileCheckBox->Value() == 1); + + tracker->SendNotices(kAskBeforeDeleteFileChanged); + Window()->PostMessage(kSettingsContentsModified); + break; + + default: + _inherited::MessageReceived(message); + break; + } +} + + +void +TrashSettingsView::SetDefaults() +{ + TrackerSettings settings; + + settings.SetDontMoveFilesToTrash(false); + settings.SetAskBeforeDeleteFile(true); + + ShowCurrentSettings(true); + // true -> send notices about the change +} + + +void +TrashSettingsView::Revert() +{ + TrackerSettings settings; + + settings.SetDontMoveFilesToTrash(fDontMoveFilesToTrash); + settings.SetAskBeforeDeleteFile(fAskBeforeDeleteFile); + + ShowCurrentSettings(true); + // true -> send notices about the change +} + + +void +TrashSettingsView::ShowCurrentSettings(bool sendNotices) +{ + TrackerSettings settings; + + fDontMoveFilesToTrashCheckBox->SetValue(settings.DontMoveFilesToTrash()); + fAskBeforeDeleteFileCheckBox->SetValue(settings.AskBeforeDeleteFile()); + + if (sendNotices) + Window()->PostMessage(kSettingsContentsModified); +} + + +void +TrashSettingsView::RecordRevertSettings() +{ + TrackerSettings settings; + + fDontMoveFilesToTrash = settings.DontMoveFilesToTrash(); + fAskBeforeDeleteFile = settings.AskBeforeDeleteFile(); +} + + +bool +TrashSettingsView::ShowsRevertSettings() const +{ + return (fDontMoveFilesToTrash == (fDontMoveFilesToTrashCheckBox->Value() > 0)) + && (fAskBeforeDeleteFile == (fAskBeforeDeleteFileCheckBox->Value() > 0)); +} + diff --git a/src/kits/tracker/SettingsViews.h b/src/kits/tracker/SettingsViews.h new file mode 100644 index 0000000000..e5ba6a1a45 --- /dev/null +++ b/src/kits/tracker/SettingsViews.h @@ -0,0 +1,253 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef _SETTINGS_VIEWS +#define _SETTINGS_VIEWS + +#include +#include +#include +#include + +#include "TrackerSettings.h" + +const uint32 kSettingsContentsModified = 'Scmo'; + +class BMenuField; +class BStringView; + + +namespace BPrivate { + +class SettingsView : public BView { +public: + SettingsView(BRect, const char *); + virtual ~SettingsView(); + + virtual void SetDefaults(); + virtual void Revert(); + virtual void ShowCurrentSettings(bool sendNotices = false); + virtual void RecordRevertSettings(); + virtual bool ShowsRevertSettings() const; +protected: + + typedef BView _inherited; +}; + +class DesktopSettingsView : public SettingsView { +public: + DesktopSettingsView(BRect); + + void MessageReceived(BMessage *); + void AttachedToWindow(); + + void SetDefaults(); + void Revert(); + void ShowCurrentSettings(bool sendNotices = false); + void RecordRevertSettings(); + bool ShowsRevertSettings() const; +private: + BRadioButton *fShowDisksIconRadioButton; + BRadioButton *fMountVolumesOntoDesktopRadioButton; + BCheckBox *fMountSharedVolumesOntoDesktopCheckBox; + BCheckBox *fIntegrateNonBootBeOSDesktopsCheckBox; + BCheckBox *fEjectWhenUnmountingCheckBox; + + bool fShowDisksIcon; + bool fMountVolumesOntoDesktop; + bool fMountSharedVolumesOntoDesktop; + bool fIntegrateNonBootBeOSDesktops; + bool fEjectWhenUnmounting; + + typedef SettingsView _inherited; +}; + +class WindowsSettingsView : public SettingsView { +public: + WindowsSettingsView(BRect); + + void MessageReceived(BMessage *); + void AttachedToWindow(); + + void SetDefaults(); + void Revert(); + void ShowCurrentSettings(bool sendNotices = false); + void RecordRevertSettings(); + bool ShowsRevertSettings() const; +private: + BCheckBox *fShowFullPathInTitleBarCheckBox; + BCheckBox *fSingleWindowBrowseCheckBox; + BCheckBox *fShowNavigatorCheckBox; + BCheckBox *fShowSelectionWhenInactiveCheckBox; + BCheckBox *fTransparentSelectionCheckBox; + BCheckBox *fSortFolderNamesFirstCheckBox; + + bool fShowFullPathInTitleBar; + bool fSingleWindowBrowse; + bool fShowNavigator; + bool fShowSelectionWhenInactive; + bool fTransparentSelection; + bool fSortFolderNamesFirst; + + typedef SettingsView _inherited; +}; + +class FilePanelSettingsView : public SettingsView { +public: + FilePanelSettingsView(BRect); + ~FilePanelSettingsView(); + + void MessageReceived(BMessage *); + void AttachedToWindow(); + + void SetDefaults(); + void Revert(); + void ShowCurrentSettings(bool sendNotices = false); + void RecordRevertSettings(); + bool ShowsRevertSettings() const; + + void GetAndRefreshDisplayedFigures() const; +private: + BCheckBox *fDesktopFilePanelRootCheckBox; + + BTextControl *fRecentApplicationsTextControl; // Not used for the moment. + BTextControl *fRecentDocumentsTextControl; + BTextControl *fRecentFoldersTextControl; + + bool fDesktopFilePanelRoot; + int32 fRecentApplications; // Not used for the moment, + int32 fRecentDocuments; + int32 fRecentFolders; + + mutable int32 fDisplayedAppCount; // Not used for the moment. + mutable int32 fDisplayedDocCount; + mutable int32 fDisplayedFolderCount; + + typedef SettingsView _inherited; +}; + +class TimeFormatSettingsView : public SettingsView { +public: + TimeFormatSettingsView(BRect); + + void MessageReceived(BMessage *); + void AttachedToWindow(); + + void SetDefaults(); + void Revert(); + void ShowCurrentSettings(bool sendNotices = false); + void RecordRevertSettings(); + bool ShowsRevertSettings() const; + + void UpdateExamples(); +private: + BRadioButton *f24HrRadioButton; + BRadioButton *f12HrRadioButton; + + BRadioButton *fYMDRadioButton; + BRadioButton *fDMYRadioButton; + BRadioButton *fMDYRadioButton; + + BMenuField *fSeparatorMenuField; + + BStringView *fLongDateExampleView; + BStringView *fShortDateExampleView; + + bool f24HrClock; + + FormatSeparator fSeparator; + DateOrder fFormat; + + typedef SettingsView _inherited; +}; + +class SpaceBarSettingsView : public SettingsView { +public: + SpaceBarSettingsView(BRect); + ~SpaceBarSettingsView(); + + void MessageReceived(BMessage *); + void AttachedToWindow(); + + void SetDefaults(); + void Revert(); + void ShowCurrentSettings(bool sendNotices = false); + void RecordRevertSettings(); + bool ShowsRevertSettings() const; + +private: + BCheckBox *fSpaceBarShowCheckBox; + BColorControl *fColorControl; + BMenuField *fColorPicker; +// BRadioButton *fUsedRadio; +// BRadioButton *fWarningRadio; +// BRadioButton *fFreeRadio; + int32 fCurrentColor; + + bool fSpaceBarShow; + rgb_color fUsedSpaceColor; + rgb_color fFreeSpaceColor; + rgb_color fWarningSpaceColor; + + typedef SettingsView _inherited; +}; + +class TrashSettingsView : public SettingsView { +public: + TrashSettingsView(BRect); + + void MessageReceived(BMessage *); + void AttachedToWindow(); + + void SetDefaults(); + void Revert(); + void ShowCurrentSettings(bool sendNotices = false); + void RecordRevertSettings(); + bool ShowsRevertSettings() const; + +private: + BCheckBox *fDontMoveFilesToTrashCheckBox; + BCheckBox *fAskBeforeDeleteFileCheckBox; + + bool fDontMoveFilesToTrash; + bool fAskBeforeDeleteFile; + + typedef SettingsView _inherited; +}; + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/SlowContextPopup.cpp b/src/kits/tracker/SlowContextPopup.cpp new file mode 100644 index 0000000000..d84457b21e --- /dev/null +++ b/src/kits/tracker/SlowContextPopup.cpp @@ -0,0 +1,561 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "Attributes.h" +#include "Commands.h" +#include "ContainerWindow.h" +#include "DesktopPoseView.h" +#include "FSUtils.h" +#include "FunctionObject.h" +#include "IconMenuItem.h" +#include "NavMenu.h" +#include "PoseView.h" +#include "QueryPoseView.h" +#include "SlowContextPopup.h" +#include "Thread.h" +#include "Tracker.h" + + +BSlowContextMenu::BSlowContextMenu(const char *title) + : BPopUpMenu(title, false, false), + fMenuBuilt(false), + fMessage(B_REFS_RECEIVED), + fParentWindow(NULL), + fItemList(NULL), + fContainer(NULL), + fTypesList(NULL), + fIsShowing(false) +{ + InitIconPreloader(); + + SetFont(be_plain_font); + SetTriggersEnabled(false); +} + + +BSlowContextMenu::~BSlowContextMenu() +{ +} + + +void +BSlowContextMenu::AttachedToWindow() +{ + // showing flag is set immediately as + // it may take a while to build the menu's + // contents. + // + // it should get set only once when Go is called + // and will get reset in DetachedFromWindow + // + // this flag is used in ContainerWindow::ShowContextMenu + // to determine whether we should show this menu, and + // the only reason we need to do this is because this + // menu is spawned ::Go as an asynchronous menu, which + // is done because we will deadlock if the target's + // window is open... so there + fIsShowing = true; + + BPopUpMenu::AttachedToWindow(); + + SpringLoadedFolderSetMenuStates(this, fTypesList); + + // allow an opportunity to reset the target for each of the items + SetTargetForItems(Target()); +} + + +void +BSlowContextMenu::DetachedFromWindow() +{ + // see note above in AttachedToWindow + fIsShowing = false; + // does this need to set this to null? + // the parent, handling dnd should set this + // appropriately + // + // if this changes, BeMenu and RecentsMenu + // in Deskbar should also change + fTypesList = NULL; + + uint32 buttons; + BPoint location; + GetMouse(&location, &buttons); + // if we are tracking a drag, + // don't send this message + if (buttons == 0) { + // send a special message to ContainerWindow + // this will occur and be used only if + // the dropped occurred in the pose (drop on an icon), + // not in the menu + BMessage message(kContextMenuDragNDrop); + ConvertToScreen(&location); + message.AddPoint("_drop_point_", location); + fMessenger.SendMessage(&message); + } +} + + +void +BSlowContextMenu::SetNavDir(const entry_ref *ref) +{ + ForceRebuild(); + // reset the slow menu building mechanism so we can add more stuff + + fNavDir = *ref; +} + + +void +BSlowContextMenu::ForceRebuild() +{ + ClearMenuBuildingState(); + fMenuBuilt = false; +} + + +bool +BSlowContextMenu::NeedsToRebuild() const +{ + return !fMenuBuilt; +} + + +void +BSlowContextMenu::ClearMenu() +{ + int32 count = CountItems(); + for (int32 index = count - 1; index >= 0; index--) + delete RemoveItem(index); + + fMenuBuilt = false; +} + + +void +BSlowContextMenu::ClearMenuBuildingState() +{ + delete fContainer; + fContainer = NULL; + + // item list is non-owning, need to delete the items because + // they didn't get added to the menu + if (fItemList) { + int32 count = fItemList->CountItems(); + for (int32 index = count - 1; index >= 0; index--) + delete RemoveItem(index); + delete fItemList; + fItemList = NULL; + } +} + +const int32 kItemsToAddChunk = 20; +const bigtime_t kMaxTimeBuildingMenu = 200000; + +bool +BSlowContextMenu::AddDynamicItem(add_state state) +{ + if (fMenuBuilt) + return false; + + if (state == B_ABORT) { + ClearMenuBuildingState(); + return false; + } + + if (state == B_INITIAL_ADD && !StartBuildingItemList()) { + ClearMenuBuildingState(); + return false; + } + + bigtime_t timeToBail = system_time() + kMaxTimeBuildingMenu; + for (int32 count = 0; count < kItemsToAddChunk; count++) { + if (!AddNextItem()) { + fMenuBuilt = true; + DoneBuildingItemList(); + ClearMenuBuildingState(); + return false; + // done with menu, don't call again + } + if (system_time() > timeToBail) + // we have been in here long enough, come back later + break; + } + + return true; // call me again, got more to show +} + + +bool +BSlowContextMenu::StartBuildingItemList() +{ + // return false when done building + BEntry entry; + + if (fNavDir.device < 0 || entry.SetTo(&fNavDir) != B_OK + || !entry.Exists()) + return false; + + fIteratingDesktop = false; + + BDirectory parent; + status_t err = entry.GetParent(&parent); + fItemList = new BObjectList(50); + + // if ref is the root item then build list of volume root dirs + fVolsOnly = (err == B_ENTRY_NOT_FOUND); + + if (fVolsOnly) + return true; + + Model startModel(&entry, true); + if (startModel.InitCheck() == B_OK) { + if (!startModel.IsContainer()) + return false; + + if (startModel.IsQuery()) + fContainer = new QueryEntryListCollection(&startModel); + else if (FSIsDeskDir(&entry)) { + fIteratingDesktop = true; + fContainer = DesktopPoseView::InitDesktopDirentIterator(0, + startModel.EntryRef()); + AddRootItemsIfNeeded(); + } else + fContainer = new DirectoryEntryList(*dynamic_cast + (startModel.Node())); + + if (fContainer->InitCheck() != B_OK) + return false; + + fContainer->Rewind(); + } + + return true; +} + + +void +BSlowContextMenu::AddRootItemsIfNeeded() +{ + BVolumeRoster roster; + roster.Rewind(); + BVolume volume; + while (roster.GetNextVolume(&volume) == B_OK) { + + BDirectory root; + BEntry entry; + if (!volume.IsPersistent() + || volume.GetRootDirectory(&root) != B_OK + || root.GetEntry(&entry) != B_OK) + continue; + + Model model(&entry); + AddOneItem(&model); + } +} + + +bool +BSlowContextMenu::AddNextItem() +{ + if (fVolsOnly) { + BuildVolumeMenu(); + return false; + } + + // limit nav menus to 500 items only + if (fItemList->CountItems() > 500) + return false; + + BEntry entry; + if (fContainer->GetNextEntry(&entry) != B_OK) + // we're finished + return false; + + Model model(&entry, true); + if (model.InitCheck() != B_OK) { +// PRINT(("not showing hidden item %s, wouldn't open\n", model->Name())); + return true; + } + + ssize_t size = -1; + PoseInfo poseInfo; + + if (model.Node()) + size = model.Node()->ReadAttr(kAttrPoseInfo, B_RAW_TYPE, 0, + &poseInfo, sizeof(poseInfo)); + + model.CloseNode(); + + // item might be in invisible + // ToDo: + // use more of PoseView's filtering here + if ((size == sizeof(poseInfo) + && !BPoseView::PoseVisible(&model, &poseInfo, false)) + || (fIteratingDesktop && !ShouldShowDesktopPose(fNavDir.device, + &model, &poseInfo))) { +// PRINT(("not showing hidden item %s\n", model.Name())); + return true; + } + + AddOneItem(&model); + return true; +} + + +void +BSlowContextMenu::AddOneItem(Model *model) +{ + BMenuItem *item = NewModelItem(model, &fMessage, fMessenger, false, + dynamic_cast(fParentWindow) ? + dynamic_cast(fParentWindow) : 0, + fTypesList, &fTrackingHook); + + if (item) + fItemList->AddItem(item); +} + + +ModelMenuItem * +BSlowContextMenu::NewModelItem(Model *model, const BMessage *invokeMessage, + const BMessenger &target, bool suppressFolderHierarchy, + BContainerWindow *parentWindow, const BObjectList *typeslist, + TrackingHookData *hook) +{ + if (model->InitCheck() != B_OK) + return NULL; + + entry_ref ref; + bool container = false; + if (model->IsSymLink()) { + + Model *newResolvedModel = NULL; + Model *result = model->LinkTo(); + + if (!result) { + newResolvedModel = new Model(model->EntryRef(), true, true); + + if (newResolvedModel->InitCheck() != B_OK) { + // broken link, still can show though, bail + delete newResolvedModel; + newResolvedModel = NULL; + } + + result = newResolvedModel; + } + + if (result) { + BModelOpener opener(result); + // open the model, if it ain't open already + + PoseInfo poseInfo; + ssize_t size = -1; + + if (result->Node()) + size = result->Node()->ReadAttr(kAttrPoseInfo, B_RAW_TYPE, 0, + &poseInfo, sizeof(poseInfo)); + + result->CloseNode(); + + if (size == sizeof(poseInfo) && !BPoseView::PoseVisible(result, + &poseInfo, false)) { + // link target sez it doesn't want to be visible, + // don't show the link + PRINT(("not showing hidden item %s\n", model->Name())); + delete newResolvedModel; + return NULL; + } + ref = *result->EntryRef(); + container = result->IsContainer(); + } + model->SetLinkTo(result); + } else { + ref = *model->EntryRef(); + container = model->IsContainer(); + } + + BMessage *message = new BMessage(*invokeMessage); + message->AddRef("refs", model->EntryRef()); + + // Truncate the name if necessary + BString truncatedString(model->Name()); + be_plain_font->TruncateString(&truncatedString, B_TRUNCATE_END, + BNavMenu::GetMaxMenuWidth()); + + ModelMenuItem *item = NULL; + if (!container || suppressFolderHierarchy) { + item = new ModelMenuItem(model, truncatedString.String(), message); + if (invokeMessage->what != B_REFS_RECEIVED) + item->SetEnabled(false); + } else { + BNavMenu *menu = new BNavMenu(truncatedString.String(), + invokeMessage->what, target, parentWindow, typeslist); + + menu->SetNavDir(&ref); + if (hook) + menu->InitTrackingHook(hook->fTrackingHook, &(hook->fTarget), + hook->fDragMessage); + + item = new ModelMenuItem(model, menu); + item->SetMessage(message); + } + + return item; +} + + +void +BSlowContextMenu::BuildVolumeMenu() +{ + BVolumeRoster roster; + BVolume volume; + + roster.Rewind(); + while (roster.GetNextVolume(&volume) == B_OK) { + + if (!volume.IsPersistent()) + continue; + + BDirectory startDir; + if (volume.GetRootDirectory(&startDir) == B_OK) { + BEntry entry; + startDir.GetEntry(&entry); + + Model *model = new Model(&entry); + if (model->InitCheck() != B_OK) { + delete model; + continue; + } + + BNavMenu *menu = new BNavMenu(model->Name(), fMessage.what, + fMessenger, fParentWindow, fTypesList); + + menu->SetNavDir(model->EntryRef()); + menu->InitTrackingHook(fTrackingHook.fTrackingHook, &(fTrackingHook.fTarget), + fTrackingHook.fDragMessage); + + ASSERT(menu->Name()); + + ModelMenuItem *item = new ModelMenuItem(model, menu); + BMessage *message = new BMessage(fMessage); + + message->AddRef("refs", model->EntryRef()); + item->SetMessage(message); + fItemList->AddItem(item); + ASSERT(item->Label()); + } + } +} + + +void +BSlowContextMenu::DoneBuildingItemList() +{ + // add sorted items to menu + if (TrackerSettings().SortFolderNamesFirst()) + fItemList->SortItems(&BNavMenu::CompareFolderNamesFirstOne); + else + fItemList->SortItems(&BNavMenu::CompareOne); + + int32 count = fItemList->CountItems(); + for (int32 index = 0; index < count; index++) + AddItem(fItemList->ItemAt(index)); + + fItemList->MakeEmpty(); + + if (!count) { + BMenuItem *item = new BMenuItem("Empty Folder", 0); + item->SetEnabled(false); + AddItem(item); + } + + SetTargetForItems(fMessenger); +} + + +void +BSlowContextMenu::SetTypesList(const BObjectList *list) +{ + fTypesList = list; +} + + +void +BSlowContextMenu::SetTarget(const BMessenger &target) +{ + fMessenger = target; +} + + +TrackingHookData * +BSlowContextMenu::InitTrackingHook(bool (*hook)(BMenu *, void *), const BMessenger *target, + const BMessage *dragMessage) +{ + fTrackingHook.fTrackingHook = hook; + if (target) + fTrackingHook.fTarget = *target; + fTrackingHook.fDragMessage = dragMessage; + SetTrackingHookDeep(this, hook, &fTrackingHook); + return &fTrackingHook; +} + + +void +BSlowContextMenu::SetTrackingHookDeep(BMenu *menu, bool (*func)(BMenu *, void *), void *state) +{ + menu->SetTrackingHook(func, state); + int32 count = menu->CountItems(); + for (int32 index = 0; index < count; index++) { + BMenuItem *item = menu->ItemAt(index); + if (!item) + continue; + + BMenu *submenu = item->Submenu(); + if (submenu) + SetTrackingHookDeep(submenu, func, state); + } +} diff --git a/src/kits/tracker/SlowContextPopup.h b/src/kits/tracker/SlowContextPopup.h new file mode 100644 index 0000000000..3d6d267a58 --- /dev/null +++ b/src/kits/tracker/SlowContextPopup.h @@ -0,0 +1,132 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef SLOW_CONTEXT_POPUP_H +#define SLOW_CONTEXT_POPUP_H + +#include +#include "NavMenu.h" + +namespace BPrivate { + +class BSlowContextMenu : public BPopUpMenu { +public: + BSlowContextMenu(const char *title); + virtual ~BSlowContextMenu(); + + virtual void AttachedToWindow(); + virtual void DetachedFromWindow(); + + void SetNavDir(const entry_ref *); + + void ClearMenu(); + + void ForceRebuild(); + bool NeedsToRebuild() const; + // will cause menu to get rebuilt next time it is shown + + void SetTarget(const BMessenger &); + const BMessenger Target() const; + + void SetTypesList(const BObjectList *list); + const BObjectList *TypesList() const; + + static ModelMenuItem *NewModelItem(Model *, const BMessage *, const BMessenger &, + bool suppressFolderHierarchy=false, BContainerWindow * = NULL, + const BObjectList *typeslist = NULL, + TrackingHookData *hook = NULL); + + TrackingHookData *InitTrackingHook(bool (*)(BMenu *, void *), + const BMessenger *target, const BMessage *dragMessage); + + const bool IsShowing() const; + +protected: + virtual bool AddDynamicItem(add_state state); + virtual bool StartBuildingItemList(); + virtual bool AddNextItem(); + virtual void DoneBuildingItemList(); + virtual void ClearMenuBuildingState(); + + void BuildVolumeMenu(); + + void AddOneItem(Model *); + void AddRootItemsIfNeeded(); + static void SetTrackingHookDeep(BMenu *, bool (*)(BMenu *, void *), void *); + + bool fMenuBuilt; + +private: + entry_ref fNavDir; + BMessage fMessage; + BMessenger fMessenger; + BWindow *fParentWindow; + + // menu building state + bool fVolsOnly; + BObjectList *fItemList; + EntryListBase *fContainer; + bool fIteratingDesktop; + + const BObjectList *fTypesList; + + TrackingHookData fTrackingHook; + bool fIsShowing; + // see note in AttachedToWindow +}; + + +} // namespace BPrivate + +using namespace BPrivate; + +inline const BObjectList * +BSlowContextMenu::TypesList() const +{ + return fTypesList; +} + +inline const BMessenger +BSlowContextMenu::Target() const +{ + return fMessenger; +} + +inline const bool +BSlowContextMenu::IsShowing() const +{ + return fIsShowing; +} + +#endif diff --git a/src/kits/tracker/SlowMenu.cpp b/src/kits/tracker/SlowMenu.cpp new file mode 100644 index 0000000000..e4705ff732 --- /dev/null +++ b/src/kits/tracker/SlowMenu.cpp @@ -0,0 +1,106 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include "SlowMenu.h" + +BSlowMenu::BSlowMenu(const char *title, menu_layout layout) + : BMenu(title, layout), + fMenuBuilt(false) +{ +} + +const int32 kItemsToAddChunk = 20; +const bigtime_t kMaxTimeBuildingMenu = 200000; + +bool +BSlowMenu::AddDynamicItem(add_state state) +{ + if (fMenuBuilt) + return false; + + if (state == B_ABORT) { + ClearMenuBuildingState(); + return false; + } + + if (state == B_INITIAL_ADD && !StartBuildingItemList()) { + ClearMenuBuildingState(); + return false; + } + + bigtime_t timeToBail = system_time() + kMaxTimeBuildingMenu; + for (int32 count = 0; count < kItemsToAddChunk; count++) { + if (!AddNextItem()) { + fMenuBuilt = true; + DoneBuildingItemList(); + ClearMenuBuildingState(); + return false; + // done with menu, don't call again + } + if (system_time() > timeToBail) + // we have been in here long enough, come back later + break; + } + + return true; // call me again, got more to show +} + +bool +BSlowMenu::StartBuildingItemList() +{ + return true; +} + +bool +BSlowMenu::AddNextItem() +{ + TRESPASS(); + // pure virtual, shouldn't be here + return true; +} + +void +BSlowMenu::DoneBuildingItemList() +{ + TRESPASS(); + // pure virtual, shouldn't be here +} + +void +BSlowMenu::ClearMenuBuildingState() +{ + TRESPASS(); + // pure virtual, shouldn't be here +} + diff --git a/src/kits/tracker/SlowMenu.h b/src/kits/tracker/SlowMenu.h new file mode 100644 index 0000000000..62e9156b07 --- /dev/null +++ b/src/kits/tracker/SlowMenu.h @@ -0,0 +1,76 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef __SLOW_MENU__ +#define __SLOW_MENU__ + +#include +#include +#include + +// SlowMenu is a convenience class that makes it easier to +// use the AddDynamicItem callback to implement a menu that can +// checks periodically between creating new items and quits +// early if needed + +namespace BPrivate { + +class BSlowMenu : public BMenu { + public: + BSlowMenu(const char *title, menu_layout layout = B_ITEMS_IN_COLUMN); + + protected: + virtual bool StartBuildingItemList(); + // set up state to start building the item list + // returns false if setup failed + virtual bool AddNextItem() = 0; + // returns false if done + virtual void DoneBuildingItemList() = 0; + // default version adds items from itemList to menu and deletes + // the list; override to sort items first, etc. + + virtual void ClearMenuBuildingState() = 0; + + protected: + virtual bool AddDynamicItem(add_state state); + // this is the callback from BMenu, you shouldn't need to override this + + bool fMenuBuilt; +}; + +} // namespace BPrivate + +using namespace BPrivate; + +#endif /* __SLOW_MENU__ */ diff --git a/src/kits/tracker/StatusWindow.cpp b/src/kits/tracker/StatusWindow.cpp new file mode 100644 index 0000000000..1a86de6435 --- /dev/null +++ b/src/kits/tracker/StatusWindow.cpp @@ -0,0 +1,690 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// A subclass of BWindow that is used to display +// the status of the tracker (Copying, Deleting, etc.). + +#include +#include +#include +#include +#include +#include + +#include + +#include "AutoLock.h" +#include "Bitmaps.h" +#include "Commands.h" +#include "StatusWindow.h" +#include "DeskWindow.h" + + +const float kDefaultStatusViewHeight = 50; +const float kUpdateGrain = 100000; +const BRect kStatusRect(200, 200, 550, 200); + + +class TCustomButton : public BButton { + public: + TCustomButton(BRect frame, uint32 command); + virtual void Draw(BRect); + private: + typedef BButton _inherited; +}; + +class BStatusMouseFilter : public BMessageFilter { + public: + BStatusMouseFilter() + : BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE, B_MOUSE_DOWN) + {} + + virtual filter_result Filter(BMessage *message, BHandler **target); +}; + + +namespace BPrivate { +BStatusWindow *gStatusWindow = NULL; +} + + +filter_result +BStatusMouseFilter::Filter(BMessage *, BHandler **target) +{ + if ((*target)->Name() + && strcmp((*target)->Name(), "StatusBar") == 0) { + BView *view = dynamic_cast(*target); + if (view) + view = view->Parent(); + if (view) + *target = view; + } + + return B_DISPATCH_MESSAGE; +} + + +TCustomButton::TCustomButton(BRect frame, uint32 what) + : BButton(frame, "", "", new BMessage(what), B_FOLLOW_LEFT | B_FOLLOW_TOP, + B_WILL_DRAW) +{ +} + + +void +TCustomButton::Draw(BRect updateRect) +{ + _inherited::Draw(updateRect); + + if (Message()->what == kStopButton) { + updateRect = Bounds(); + updateRect.InsetBy(9, 7); + SetHighColor(0, 0, 0); + FillRect(updateRect); + } else { + updateRect = Bounds(); + updateRect.InsetBy(9, 6); + BRect rect(updateRect); + rect.right -= 3; + + updateRect.left += 3; + updateRect.OffsetBy(1, 0); + SetHighColor(0, 0, 0); + FillRect(updateRect); + FillRect(rect); + } +} + + +BStatusWindow::BStatusWindow() + : BWindow(kStatusRect, "Tracker Status", B_TITLED_WINDOW, + B_NOT_CLOSABLE | B_NOT_RESIZABLE | B_NOT_ZOOMABLE, + B_ALL_WORKSPACES), + fRetainDesktopFocus(false) +{ + SetSizeLimits(0, 100000, 0, 100000); + fMouseDownFilter = new BStatusMouseFilter(); + AddCommonFilter(fMouseDownFilter); + + BRect bounds(Bounds()); + + BView *view = new BView(bounds, "BackView", B_FOLLOW_ALL, B_WILL_DRAW); + view->SetViewColor(216, 216, 216); + AddChild(view); + + Run(); +} + + +BStatusWindow::~BStatusWindow() +{ +} + + +bool +BStatusWindow::CheckCanceledOrPaused(thread_id thread) +{ + bool wasCanceled = false; + bool isPaused = false; + + BStatusView *view = NULL; + + for (;;) { + + AutoLock lock(this); + // check if cancel or pause hit + for (int32 index = fViewList.CountItems() - 1; index >= 0; index--) { + + view = fViewList.ItemAt(index); + if (view && view->Thread() == thread) { + isPaused = view->IsPaused(); + wasCanceled = view->WasCanceled(); + break; + } + } + lock.Unlock(); + + if (wasCanceled || !isPaused) + break; + + if (isPaused && view) { + AutoLock lock(this); + // say we are paused + view->Invalidate(); + lock.Unlock(); + + ASSERT(find_thread(NULL) == view->Thread()); + + // and suspend ourselves + // we will get resumend from BStatusView::MessageReceived + suspend_thread(view->Thread()); + } + break; + + } + + return wasCanceled; +} + + +bool +BStatusWindow::AttemptToQuit() +{ + // called when tracker is quitting + // try to cancel all the move/copy/empty trash threads in a nice way + // by issuing cancels + int32 count = fViewList.CountItems(); + + if (count == 0) + return true; + + for (int32 index = 0; index < count; index++) + fViewList.ItemAt(index)->SetWasCanceled(); + + // maybe next time everything will have been canceled + return false; +} + + +void +BStatusWindow::CreateStatusItem(thread_id thread, StatusWindowState type) +{ + AutoLock lock(this); + + BRect rect(Bounds()); + if (BStatusView* lastView = fViewList.LastItem()) + rect.top = lastView->Frame().bottom + 1; + rect.bottom = rect.top + kDefaultStatusViewHeight - 1; + + BStatusView *view = new BStatusView(rect, thread, type); + // the BStatusView will resize itself if needed in its constructor + ChildAt(0)->AddChild(view); + fViewList.AddItem(view); + + ResizeTo(Bounds().Width(), view->Frame().bottom); + + // find out if the desktop is the active window + // if the status window is the only thing to take over active state and + // desktop was active to begin with, return focus back to desktop + // when we are done + bool desktopActive = false; + { + AutoLock lock(be_app); + int32 count = be_app->CountWindows(); + for (int32 index = 0; index < count; index++) + if (dynamic_cast(be_app->WindowAt(index)) + && be_app->WindowAt(index)->IsActive()) { + desktopActive = true; + break; + } + } + + if (IsHidden()) { + fRetainDesktopFocus = desktopActive; + Minimize(false); + Show(); + } else + fRetainDesktopFocus &= desktopActive; +} + + +void +BStatusWindow::WindowActivated(bool state) +{ + if (!state) + fRetainDesktopFocus = false; + + return _inherited::WindowActivated(state); +} + + +void +BStatusWindow::RemoveStatusItem(thread_id thread) +{ + AutoLock lock(this); + BStatusView *winner = NULL; + + int32 numItems = fViewList.CountItems(); + int32 index; + for (index = 0; index < numItems; index++) { + BStatusView *view = fViewList.ItemAt(index); + if (view->Thread() == thread) { + winner = view; + break; + } + } + + if (winner) { + // the height by which the other views will have to be moved (in pixel count) + float height = winner->Bounds().Height() + 1; + fViewList.RemoveItem(winner); + winner->RemoveSelf(); + delete winner; + + if (--numItems == 0 && !IsHidden()) { + BDeskWindow *desktop = NULL; + if (fRetainDesktopFocus) { + AutoLock lock(be_app); + int32 count = be_app->CountWindows(); + for (int32 index = 0; index < count; index++) { + desktop = dynamic_cast(be_app->WindowAt(index)); + if (desktop) + break; + } + } + Hide(); + if (desktop) + // desktop was active when we first started, + // make it active again + desktop->Activate(); + } + + for (; index < numItems; index++) + fViewList.ItemAt(index)->MoveBy(0, -height); + + ResizeTo(Bounds().Width(), Bounds().Height() - height); + } + +} + + +bool +BStatusWindow::HasStatus(thread_id thread) +{ + AutoLock lock(this); + + int32 numItems = fViewList.CountItems(); + for (int32 index = 0; index < numItems; index++) { + BStatusView *view = fViewList.ItemAt(index); + if (view->Thread() == thread) + return true; + + } + + return false; +} + + +void +BStatusWindow::UpdateStatus(thread_id thread, const char *curItem, off_t itemSize, + bool optional) +{ + AutoLock lock(this); + + int32 numItems = fViewList.CountItems(); + for (int32 index = 0; index < numItems; index++) { + BStatusView *view = fViewList.ItemAt(index); + if (view->Thread() == thread) { + view->UpdateStatus(curItem, itemSize, optional); + break; + } + } + +} + + +void +BStatusWindow::InitStatusItem(thread_id thread, int32 totalItems, off_t totalSize, + const entry_ref *destDir, bool showCount) +{ + AutoLock lock(this); + + int32 numItems = fViewList.CountItems(); + for (int32 index = 0; index < numItems; index++) { + BStatusView *view = fViewList.ItemAt(index); + if (view->Thread() == thread) { + view->InitStatus(totalItems, totalSize, destDir, showCount); + break; + } + } + +} + + +BStatusView::BStatusView(BRect bounds, thread_id thread, StatusWindowState type) + : BView(bounds, "StatusView", B_FOLLOW_NONE, B_WILL_DRAW), + fBitmap(NULL) +{ + Init(); + + fThread = thread; + fType = type; + + SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + SetLowColor(ViewColor()); + SetHighColor(20, 20, 20); + SetDrawingMode(B_OP_OVER); + + BRect rect(bounds); + rect.OffsetTo(B_ORIGIN); + rect.left += 40; + rect.right -= 50; + rect.top += 6; + rect.bottom = rect.top + 15; + + + const char *caption = NULL; + int32 id = 0; + + switch (type) { + case kCopyState: + caption = "Preparing to copy items" B_UTF8_ELLIPSIS; + id = kResCopyStatusBitmap; + break; + + case kMoveState: + caption = "Preparing to move items" B_UTF8_ELLIPSIS; + id = kResMoveStatusBitmap; + break; + + case kCreateLinkState: + caption = "Preparing to create links" B_UTF8_ELLIPSIS; + id = kResMoveStatusBitmap; + break; + + case kTrashState: + caption = "Preparing to empty Trash" B_UTF8_ELLIPSIS; + id = kResTrashStatusBitmap; + break; + + + case kVolumeState: + caption = "Searching for disks to mount" B_UTF8_ELLIPSIS; + break; + + case kDeleteState: + caption = "Preparing to delete items" B_UTF8_ELLIPSIS; + id = kResTrashStatusBitmap; + break; + + case kRestoreFromTrashState: + caption = "Preparing to restore items" B_UTF8_ELLIPSIS; + break; + + default: + TRESPASS(); + break; + } + + if (caption) { + fStatusBar = new BStatusBar(rect, "StatusBar", caption); + fStatusBar->SetBarHeight(12); + float width, height; + fStatusBar->GetPreferredSize(&width, &height); + fStatusBar->ResizeTo(fStatusBar->Frame().Width(), height); + AddChild(fStatusBar); + + // figure out how much room we need to display + // the additional status message below the bar + font_height fh; + GetFontHeight(&fh); + BRect f = fStatusBar->Frame(); + // height is 3 x the "room from the top" + bar height + room for string + ResizeTo(Bounds().Width(), f.top + f.Height() + fh.leading + fh.ascent + fh.descent + f.top); + } + + if (id) + GetTrackerResources()->GetBitmapResource(B_MESSAGE_TYPE, id, &fBitmap); + + + rect = Bounds(); + rect.left = rect.right - 46; + rect.right = rect.left + 17; + rect.top += 17; + rect.bottom = rect.top + 10; + + fPauseButton = new TCustomButton(rect, kPauseButton); + fPauseButton->ResizeTo(22, 18); + AddChild(fPauseButton); + + rect.OffsetBy(20, 0); + fStopButton = new TCustomButton(rect, kStopButton); + fStopButton->ResizeTo(22, 18); + AddChild(fStopButton); +} + + +BStatusView::~BStatusView() +{ + delete fBitmap; +} + + +void +BStatusView::Init() +{ + fDestDir = ""; + fCurItem = 0; + fPendingStatusString[0] = '\0'; + fWasCanceled = false; + fIsPaused = false; + fLastUpdateTime = 0; + fItemSize = 0; +} + + +void +BStatusView::AttachedToWindow() +{ + fPauseButton->SetTarget(this); + fStopButton->SetTarget(this); +} + + +void +BStatusView::InitStatus(int32 totalItems, off_t totalSize, + const entry_ref *destDir, bool showCount) +{ + Init(); + fTotalSize = totalSize; + fShowCount = showCount; + + BEntry entry; + char name[B_FILE_NAME_LENGTH]; + if (destDir && (entry.SetTo(destDir) == B_OK)) { + entry.GetName(name); + fDestDir = name; + } + + BString buffer; + buffer << "of " << totalItems; + + switch (fType) { + case kCopyState: + fStatusBar->Reset("Copying: ", buffer.String()); + break; + + case kCreateLinkState: + fStatusBar->Reset("Creating Links: ", buffer.String()); + break; + + case kMoveState: + fStatusBar->Reset("Moving: ", buffer.String()); + break; + + case kTrashState: + fStatusBar->Reset("Emptying Trash" B_UTF8_ELLIPSIS " ", buffer.String()); + break; + + case kDeleteState: + fStatusBar->Reset("Deleting: ", buffer.String()); + break; + + case kRestoreFromTrashState: + fStatusBar->Reset("Restoring: ", buffer.String()); + break; + + default: + break; + } + + fStatusBar->SetMaxValue(1); + // SetMaxValue has to be here because Reset changes it to 100 + Invalidate(); +} + + +void +BStatusView::UpdateStatus(const char *curItem, off_t itemSize, bool optional) +{ + float currentTime = system_time(); + + if (fShowCount) { + + if (curItem) + fCurItem++; + + fItemSize += itemSize; + + if (!optional || ((currentTime - fLastUpdateTime) > kUpdateGrain)) { + if (curItem != NULL || fPendingStatusString[0]) { + // forced update or past update time + + BString buffer; + buffer << fCurItem << " "; + + // if we don't have curItem, take the one from the stash + const char *statusItem = curItem != NULL + ? curItem : fPendingStatusString; + + fStatusBar->Update((float)fItemSize / fTotalSize, statusItem, buffer.String()); + + // we already displayed this item, clear the stash + fPendingStatusString[0] = '\0'; + + fLastUpdateTime = currentTime; + } + else + // don't have a file to show, just update the bar + fStatusBar->Update((float)fItemSize / fTotalSize); + + fItemSize = 0; + } else if (curItem != NULL) { + // stash away the name of the item we are currently processing + // so we can show it when the time comes + strncpy(fPendingStatusString, curItem, 127); + fPendingStatusString[127] = '0'; + } + } else { + fStatusBar->Update((float)fItemSize / fTotalSize); + fItemSize = 0; + } +} + + +void +BStatusView::MessageReceived(BMessage *message) +{ + switch (message->what) { + case kPauseButton: + fIsPaused = !fIsPaused; + if (!fIsPaused) { + + // force window update + Invalidate(); + + // let 'er rip + resume_thread(Thread()); + } + break; + + case kStopButton: + fWasCanceled = true; + if (fIsPaused) { + // resume so that the copy loop gets a chance to finish up + fIsPaused = false; + + // force window update + Invalidate(); + + // let 'er rip + resume_thread(Thread()); + } + break; + + default: + _inherited::MessageReceived(message); + break; + } +} + + +void +BStatusView::Draw(BRect) +{ + if (fBitmap) + DrawBitmap(fBitmap, BPoint(4, 10)); + + BRect bounds(Bounds()); + + // draw a frame, which also separates multiple BStatusViews + rgb_color light = tint_color(ViewColor(), B_LIGHTEN_MAX_TINT); + rgb_color shadow = tint_color(ViewColor(), B_DARKEN_1_TINT); + BeginLineArray(4); + AddLine(BPoint(bounds.left, bounds.bottom - 1.0), + BPoint(bounds.left, bounds.top), light); + AddLine(BPoint(bounds.left + 1.0, bounds.top), + BPoint(bounds.right, bounds.top), light); + AddLine(BPoint(bounds.right, bounds.top + 1.0), + BPoint(bounds.right, bounds.bottom), shadow); + AddLine(BPoint(bounds.right - 1.0, bounds.bottom), + BPoint(bounds.left, bounds.bottom), shadow); + EndLineArray(); + + SetHighColor(0, 0, 0); + + BPoint tp = fStatusBar->Frame().LeftBottom(); + font_height fh; + GetFontHeight(&fh); + tp.x += 2; + tp.y += fh.leading + fh.ascent; + MovePenTo(tp); + + if (IsPaused()) + DrawString("Paused: click to resume or stop"); + else if (fDestDir.Length()) { + BString buffer; + buffer << "To: " << fDestDir; + SetHighColor(0, 0, 0); + DrawString(buffer.String()); + } +} + + +void +BStatusView::SetWasCanceled() +{ + fWasCanceled = true; +} + diff --git a/src/kits/tracker/StatusWindow.h b/src/kits/tracker/StatusWindow.h new file mode 100644 index 0000000000..c3f6afe2f9 --- /dev/null +++ b/src/kits/tracker/StatusWindow.h @@ -0,0 +1,161 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef STATUS_WINDOW_H +#define STATUS_WINDOW_H + +#include +#include +#include +#include +#include + +#include "ObjectList.h" + +namespace BPrivate { + +enum StatusWindowState { + kCopyState, + kMoveState, + kDeleteState, + kTrashState, + kVolumeState, + kCreateLinkState, + kRestoreFromTrashState +}; + +class BStatusView; + +class BStatusWindow : public BWindow { +public: + BStatusWindow(); + ~BStatusWindow(); + void CreateStatusItem(thread_id, StatusWindowState); + void InitStatusItem(thread_id, int32 totalItems, off_t totalSize, + const entry_ref *destDir = NULL, bool showCount = true); + void UpdateStatus(thread_id, const char *curItem, off_t itemSize, bool optional = false); + // if true is passed in status will only + // be updated if 0.2 seconds elapsed since the last update + void RemoveStatusItem(thread_id); + bool HasStatus(thread_id); + bool CheckCanceledOrPaused(thread_id); + void UpdateButtonState(); + + bool AttemptToQuit(); + // called by the tracker app during quit time, before + // inherited QuitRequested; kills all the copy/move/empty trash + // threads in a clean way by issuing a cancel +protected: + virtual void WindowActivated(bool state); + +private: + BObjectList fViewList; + BMessageFilter *fMouseDownFilter; + + bool fRetainDesktopFocus; + + typedef BWindow _inherited; +}; + +class BStatusView : public BView { +public: + BStatusView(BRect, thread_id, StatusWindowState); + virtual ~BStatusView(); + + void Init(); + + void InitStatus(int32 totalItems, off_t totalSize, const entry_ref *destDir, + bool showCount); + + // BView overrides + virtual void Draw(BRect); + virtual void AttachedToWindow(); + virtual void MessageReceived(BMessage *); + void UpdateStatus(const char *curItem, off_t itemSize, bool optional = false); + // if true is passed in status will only + // be updated if 0.2 seconds elapsed since the last update + + bool WasCanceled() const; + bool IsPaused() const; + thread_id Thread() const; + + void ForceQuit(); + void SetWasCanceled(); + // called by AboutToQuit + +private: + BStatusBar *fStatusBar; + off_t fTotalSize; + off_t fItemSize; + int32 fCurItem; + int32 fType; + BBitmap *fBitmap; + BButton *fStopButton; + BButton *fPauseButton; + thread_id fThread; + float fLastUpdateTime; + bool fShowCount; + bool fWasCanceled; + bool fIsPaused; + BString fDestDir; + char fPendingStatusString[128]; + + typedef BView _inherited; +}; + +inline bool +BStatusView::IsPaused() const +{ + return fIsPaused; +} + +inline bool +BStatusView::WasCanceled() const +{ + return fWasCanceled; +} + +inline thread_id +BStatusView::Thread() const +{ + return fThread; +} + +extern BStatusWindow *gStatusWindow; + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/TaskLoop.cpp b/src/kits/tracker/TaskLoop.cpp new file mode 100644 index 0000000000..fe10f58ee2 --- /dev/null +++ b/src/kits/tracker/TaskLoop.cpp @@ -0,0 +1,591 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include + +#include "AutoLock.h" +#include "TaskLoop.h" + + +DelayedTask::DelayedTask(bigtime_t delay) + : fRunAfter(system_time() + delay) +{ +} + +DelayedTask::~DelayedTask() +{ +} + +OneShotDelayedTask::OneShotDelayedTask(FunctionObject *functor, bigtime_t delay) + : DelayedTask(delay), + fFunctor(functor) +{ +} + + +OneShotDelayedTask::~OneShotDelayedTask() +{ + delete fFunctor; +} + + +bool +OneShotDelayedTask::RunIfNeeded(bigtime_t currentTime) +{ + if (currentTime < fRunAfter) + return false; + + (*fFunctor)(); + return true; +} + + +PeriodicDelayedTask::PeriodicDelayedTask(FunctionObjectWithResult *functor, + bigtime_t initialDelay, bigtime_t period) + : DelayedTask(initialDelay), + fPeriod(period), + fFunctor(functor) +{ +} + + +PeriodicDelayedTask::~PeriodicDelayedTask() +{ + delete fFunctor; +} + + +bool +PeriodicDelayedTask::RunIfNeeded(bigtime_t currentTime) +{ + if (!currentTime < fRunAfter) + return false; + + fRunAfter = currentTime + fPeriod; + (*fFunctor)(); + return fFunctor->Result(); +} + + +PeriodicDelayedTaskWithTimeout::PeriodicDelayedTaskWithTimeout( + FunctionObjectWithResult *functor, bigtime_t initialDelay, + bigtime_t period, bigtime_t timeout) + : PeriodicDelayedTask(functor, initialDelay, period), + fTimeoutAfter(system_time() + timeout) +{ +} + + +bool +PeriodicDelayedTaskWithTimeout::RunIfNeeded(bigtime_t currentTime) +{ + if (currentTime < fRunAfter) + return false; + + fRunAfter = currentTime + fPeriod; + (*fFunctor)(); + if (fFunctor->Result()) + return true; + + // if call didn't terminate the task yet, check if timeout is due + return currentTime > fTimeoutAfter; +} + + +RunWhenIdleTask::RunWhenIdleTask(FunctionObjectWithResult *functor, bigtime_t + initialDelay, bigtime_t idleFor, bigtime_t heartBeat) + : PeriodicDelayedTask(functor, initialDelay, heartBeat), + fIdleFor(idleFor), + fState(kInitialDelay) +{ +} + + +RunWhenIdleTask::~RunWhenIdleTask() +{ +} + + +bool +RunWhenIdleTask::RunIfNeeded(bigtime_t currentTime) +{ + if (currentTime < fRunAfter) + return false; + + fRunAfter = currentTime + fPeriod; + // PRINT(("runWhenIdle: runAfter %Ld, current time %Ld, period %Ld\n", + // fRunAfter, currentTime, fPeriod)); + + if (fState == kInitialDelay) { +// PRINT(("run when idle task - past intial delay\n")); + ResetIdleTimer(currentTime); + } else if (fState == kInIdleState && !StillIdle(currentTime)) { + fState = kInitialIdleWait; + ResetIdleTimer(currentTime); + } else if (fState != kInitialIdleWait || IdleTimerExpired(currentTime)) { + fState = kInIdleState; + (*fFunctor)(); + return fFunctor->Result(); + } + return false; +} + + +static bigtime_t +ActivityLevel() +{ + // stolen from roster server + bigtime_t time = 0; + system_info sinfo; + get_system_info(&sinfo); + for (int32 index = 0; index < sinfo.cpu_count; index++) + time += sinfo.cpu_infos[index].active_time; + return time / ((bigtime_t) sinfo.cpu_count); +} + + +void +RunWhenIdleTask::ResetIdleTimer(bigtime_t currentTime) +{ + fActivityLevel = ActivityLevel(); + fActivityLevelStart = currentTime; + fLastCPUTooBusyTime = currentTime; + fState = kInitialIdleWait; +} + +const float kTaskOverhead = 0.01f; + // this should really be specified by the task itself +const float kIdleTreshold = 0.15f; + +bool +RunWhenIdleTask::IsIdle(bigtime_t currentTime, float taskOverhead) +{ + bigtime_t currentActivityLevel = ActivityLevel(); + float load = (float)(currentActivityLevel - fActivityLevel) + / (float)(currentTime - fActivityLevelStart); + + fActivityLevel = currentActivityLevel; + fActivityLevelStart = currentTime; + + load -= taskOverhead; + + bool idle = true; + + if (load > kIdleTreshold) { +// PRINT(("not idle enough %f\n", load)); + idle = false; + } else if ((currentTime - fLastCPUTooBusyTime) < fIdleFor + || idle_time() < fIdleFor) { +// PRINT(("load %f, not idle long enough %Ld, %Ld\n", load, +// currentTime - fLastCPUTooBusyTime, +// idle_time())); + idle = false; + } + +#if xDEBUG + else + PRINT(("load %f, idle for %Ld sec, go\n", load, + (currentTime - fLastCPUTooBusyTime) / 1000000)); +#endif + + return idle; +} + + +bool +RunWhenIdleTask::IdleTimerExpired(bigtime_t currentTime) +{ + return IsIdle(currentTime, 0); +} + + +bool +RunWhenIdleTask::StillIdle(bigtime_t currentTime) +{ + return IsIdle(currentTime, kIdleTreshold); +} + + +TaskLoop::TaskLoop(bigtime_t heartBeat) + : fTaskList(10, true), + fHeartBeat(heartBeat) +{ +} + + +TaskLoop::~TaskLoop() +{ +} + + +void +TaskLoop::RunLater(DelayedTask *task) +{ + AddTask(task); +} + + +void +TaskLoop::RunLater(FunctionObject *functor, bigtime_t delay) +{ + RunLater(new OneShotDelayedTask(functor, delay)); +} + + +void +TaskLoop::RunLater(FunctionObjectWithResult *functor, + bigtime_t delay, bigtime_t period) +{ + RunLater(new PeriodicDelayedTask(functor, delay, period)); +} + + +void +TaskLoop::RunLater(FunctionObjectWithResult *functor, bigtime_t delay, + bigtime_t period, bigtime_t timeout) +{ + RunLater(new PeriodicDelayedTaskWithTimeout(functor, delay, period, timeout)); +} + + +void +TaskLoop::RunWhenIdle(FunctionObjectWithResult *functor, bigtime_t initialDelay, + bigtime_t idleTime, bigtime_t heartBeat) +{ + RunLater(new RunWhenIdleTask(functor, initialDelay, idleTime, heartBeat)); +} + + +class AccumulatedOneShotDelayedTask : public OneShotDelayedTask { + // supports accumulating functors +public: + AccumulatedOneShotDelayedTask(AccumulatingFunctionObject *functor, bigtime_t delay, + bigtime_t maxAccumulatingTime = 0, int32 maxAccumulateCount = 0) + : OneShotDelayedTask(functor, delay), + maxAccumulateCount(maxAccumulateCount), + accumulateCount(1), + maxAccumulatingTime(maxAccumulatingTime), + initialTime(system_time()) + {} + + bool CanAccumulate(const AccumulatingFunctionObject *accumulateThis) const + { + if (maxAccumulateCount && accumulateCount > maxAccumulateCount) + // don't accumulate if too may accumulated already + return false; + + if (maxAccumulatingTime && system_time() > initialTime + maxAccumulatingTime) + // don't accumulate if too late past initial task + return false; + + return static_cast(fFunctor)->CanAccumulate(accumulateThis); + } + + virtual void Accumulate(AccumulatingFunctionObject *accumulateThis, bigtime_t delay) + { + fRunAfter = system_time() + delay; + // reset fRunAfter + accumulateCount++; + static_cast(fFunctor)->Accumulate(accumulateThis); + } + +private: + int32 maxAccumulateCount; + int32 accumulateCount; + bigtime_t maxAccumulatingTime; + bigtime_t initialTime; +}; + +void +TaskLoop::AccumulatedRunLater(AccumulatingFunctionObject *functor, bigtime_t delay, + bigtime_t maxAccumulatingTime, int32 maxAccumulateCount) +{ + AutoLock autoLock(&fLock); + if (!autoLock.IsLocked()) { + return; + } + int32 count = fTaskList.CountItems(); + for (int32 index = 0; index < count; index++) { + AccumulatedOneShotDelayedTask *task = dynamic_cast + (fTaskList.ItemAt(index)); + if (!task) + continue; + + if (task->CanAccumulate(functor)) { + task->Accumulate(functor, delay); + return; + } + } + RunLater(new AccumulatedOneShotDelayedTask(functor, delay, maxAccumulatingTime, + maxAccumulateCount)); +} + + +bool +TaskLoop::Pulse() +{ + ASSERT(fLock.IsLocked()); + + int32 count = fTaskList.CountItems(); + if (count > 0) { + bigtime_t currentTime = system_time(); + for (int32 index = 0; index < count; ) { + DelayedTask *task = fTaskList.ItemAt(index); + // give every task a try + if (task->RunIfNeeded(currentTime)) { + // if done, remove from list + RemoveTask(task); + count--; + } else + index++; + } + } + return count == 0 && !KeepPulsingWhenEmpty(); +} + +const bigtime_t kInfinity = B_INFINITE_TIMEOUT; + +bigtime_t +TaskLoop::LatestRunTime() const +{ + ASSERT(fLock.IsLocked()); + bigtime_t result = kInfinity; + +#if xDEBUG + DelayedTask *nextTask = 0; +#endif + int32 count = fTaskList.CountItems(); + for (int32 index = 0; index < count; index++) { + bigtime_t runAfter = fTaskList.ItemAt(index)->RunAfterTime(); + if (runAfter < result) { + result = runAfter; + +#if xDEBUG + nextTask = fTaskList.ItemAt(index); +#endif + } + } + + +#if xDEBUG + if (nextTask) + PRINT(("latestRunTime : next task %s\n", typeid(*nextTask).name)); + else + PRINT(("latestRunTime : no next task\n")); +#endif + + return result; +} + + +void +TaskLoop::RemoveTask(DelayedTask *task) +{ + ASSERT(fLock.IsLocked()); + // remove the task + fTaskList.RemoveItem(task); +} + +void +TaskLoop::AddTask(DelayedTask *task) +{ + AutoLock autoLock(&fLock); + if (!autoLock.IsLocked()) { + delete task; + return; + } + + fTaskList.AddItem(task); + StartPulsingIfNeeded(); +} + + +StandAloneTaskLoop::StandAloneTaskLoop(bool keepThread, bigtime_t heartBeat) + : TaskLoop(heartBeat), + fNeedToQuit(false), + fScanThread(-1), + fKeepThread(keepThread) +{ +} + + +StandAloneTaskLoop::~StandAloneTaskLoop() +{ + fLock.Lock(); + fNeedToQuit = true; + bool easyOut = (fScanThread == -1); + fLock.Unlock(); + + if (!easyOut) + for (int32 timeout = 10000; ; timeout--) { + // use a 10 sec timeout value in case the spawned + // thread is stuck somewhere + + if (!timeout) { + PRINT(("StandAloneTaskLoop timed out, quitting abruptly")); + break; + } + + bool done; + + fLock.Lock(); + done = (fScanThread == -1); + fLock.Unlock(); + if (done) + break; + + snooze(1000); + } +} + +void +StandAloneTaskLoop::StartPulsingIfNeeded() +{ + ASSERT(fLock.IsLocked()); + if (fScanThread < 0) { + // no loop thread yet, spawn one + fScanThread = spawn_thread(StandAloneTaskLoop::RunBinder, "TrackerTaskLoop", + B_LOW_PRIORITY, this); + resume_thread(fScanThread); + } +} + +bool +StandAloneTaskLoop::KeepPulsingWhenEmpty() const +{ + return fKeepThread; +} + +status_t +StandAloneTaskLoop::RunBinder(void *castToThis) +{ + StandAloneTaskLoop *self = (StandAloneTaskLoop *)castToThis; + self->Run(); + return B_OK; +} + +void +StandAloneTaskLoop::Run() +{ + for(;;) { + AutoLock autoLock(&fLock); + if (!autoLock) + return; + + if (fNeedToQuit) { + // task loop being deleted, let go of the thread allowing the + // to go through deletion + fScanThread = -1; + return; + } + + if (Pulse()) { + fScanThread = -1; + return; + } + + // figure out when to run next by checking out when the different + // tasks wan't to be woken up, snooze until a little bit before that + // time + bigtime_t now = system_time(); + bigtime_t latestRunTime = LatestRunTime() - 1000; + bigtime_t afterHeartBeatTime = now + fHeartBeat; + bigtime_t snoozeTill = latestRunTime < afterHeartBeatTime ? + latestRunTime : afterHeartBeatTime; + + autoLock.Unlock(); + + if (snoozeTill > now) + snooze_until(snoozeTill, B_SYSTEM_TIMEBASE); + else + snooze(1000); + } +} + +void +StandAloneTaskLoop::AddTask(DelayedTask *delayedTask) +{ + _inherited::AddTask(delayedTask); + if (fScanThread < 0) + return; + + // wake up the loop thread if it is asleep + thread_info info; + get_thread_info(fScanThread, &info); + if (info.state == B_THREAD_ASLEEP) { + suspend_thread(fScanThread); + snooze(1000); // snooze because BeBook sez so + resume_thread(fScanThread); + } +} + +PiggybackTaskLoop::PiggybackTaskLoop(bigtime_t heartBeat) + : TaskLoop(heartBeat), + fNextHeartBeatTime(0), + fPulseMe(false) +{ +} + + +PiggybackTaskLoop::~PiggybackTaskLoop() +{ +} + +void +PiggybackTaskLoop::PulseMe() +{ + if (!fPulseMe) + return; + + bigtime_t time = system_time(); + if (fNextHeartBeatTime < time) { + AutoLock autoLock(&fLock); + if (Pulse()) + fPulseMe = false; + fNextHeartBeatTime = time + fHeartBeat; + } +} + +bool +PiggybackTaskLoop::KeepPulsingWhenEmpty() const +{ + return false; +} + +void +PiggybackTaskLoop::StartPulsingIfNeeded() +{ + fPulseMe = true; +} + diff --git a/src/kits/tracker/TaskLoop.h b/src/kits/tracker/TaskLoop.h new file mode 100644 index 0000000000..e0b65ecbef --- /dev/null +++ b/src/kits/tracker/TaskLoop.h @@ -0,0 +1,254 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// +// Delayed Tasks, Periodic Delayed Tasks, Periodic Delayed Tasks with timeout, +// Run when idle tasks, accumulating delayed tasks +// + + +#ifndef __TASK_LOOP__ +#define __TASK_LOOP__ + +#include + +#include "FunctionObject.h" +#include "ObjectList.h" + +namespace BPrivate { + +// Task flavors + +class DelayedTask { +public: + DelayedTask(bigtime_t delay); + virtual ~DelayedTask(); + + virtual bool RunIfNeeded(bigtime_t currentTime) = 0; + // returns true if done and should not be called again + + bigtime_t RunAfterTime() const; + +protected: + bigtime_t fRunAfter; +}; + +class OneShotDelayedTask : public DelayedTask { +// called once after a specified delay +public: + OneShotDelayedTask(FunctionObject *functor, bigtime_t delay); + virtual ~OneShotDelayedTask(); + + virtual bool RunIfNeeded(bigtime_t currentTime); + +protected: + FunctionObject *fFunctor; +}; + +class PeriodicDelayedTask : public DelayedTask { +// called periodically till functor return true +public: + PeriodicDelayedTask(FunctionObjectWithResult *functor, + bigtime_t initialDelay, bigtime_t period); + virtual ~PeriodicDelayedTask(); + + virtual bool RunIfNeeded(bigtime_t currentTime); + +protected: + bigtime_t fPeriod; + FunctionObjectWithResult *fFunctor; +}; + +class PeriodicDelayedTaskWithTimeout : public PeriodicDelayedTask { +// called periodically till functor returns true or till time out +public: + PeriodicDelayedTaskWithTimeout(FunctionObjectWithResult *functor, + bigtime_t initialDelay, bigtime_t period, bigtime_t timeout); + + virtual bool RunIfNeeded(bigtime_t currentTime); + +protected: + bigtime_t fTimeoutAfter; +}; + +class RunWhenIdleTask : public PeriodicDelayedTask { +// after initial delay starts periodically calling functor if system is idle +// until functor returns true +public: + RunWhenIdleTask(FunctionObjectWithResult *functor, bigtime_t initialDelay, + bigtime_t idleFor, bigtime_t heartBeat); + virtual ~RunWhenIdleTask(); + + virtual bool RunIfNeeded(bigtime_t currentTime); + +protected: + void ResetIdleTimer(bigtime_t currentTime); + bool IdleTimerExpired(bigtime_t currentTime); + bool StillIdle(bigtime_t currentTime); + bool IsIdle(bigtime_t currentTime, float taskOverhead); + + bigtime_t fIdleFor; + + enum State { + kInitialDelay, + kInitialIdleWait, + kInIdleState + }; + + State fState; + bigtime_t fActivityLevelStart; + bigtime_t fActivityLevel; + bigtime_t fLastCPUTooBusyTime; + +private: + typedef PeriodicDelayedTask _inherited; +}; + +class AccumulatingFunctionObject : public FunctionObject { + // This class is used for clumping up function objects that + // can be done as a single object. For instance the mime + // notification mechanism sends out multiple notifications on + // a single change and we need to accumulate the resulting + // icon update into a single one +public: + virtual bool CanAccumulate(const AccumulatingFunctionObject *) const = 0; + virtual void Accumulate(AccumulatingFunctionObject *) = 0; +}; + + +// task loop is a separate thread that hosts tasks that keep getting called +// periodically; if a task returns true, it is done - it gets removed from +// the list and deleted +class TaskLoop { +public: + TaskLoop(bigtime_t heartBeat = 10000); + virtual ~TaskLoop(); + + void RunLater(DelayedTask *); + void RunLater(FunctionObject *functor, bigtime_t delay); + // execute a function object after a delay + + void RunLater(FunctionObjectWithResult *functor, bigtime_t delay, + bigtime_t period); + // periodically execute function object after initial delay until function + // object returns true + + void RunLater(FunctionObjectWithResult *functor, bigtime_t delay, + bigtime_t period, bigtime_t timeout); + // periodically execute function object after initial delay until function + // object returns true or timeout is reached + + void AccumulatedRunLater(AccumulatingFunctionObject *functor, bigtime_t delay, + bigtime_t maxAccumulatingTime = 0, int32 maxAccumulateCount = 0); + // will search the delayed task loop for other accumulating functors + // and will accumulate with them, else will create a new delayed task + // the task will no longer accumulate if past the delay + // unless is zero + // no more than will get accumulated, unless + // is zero + + void RunWhenIdle(FunctionObjectWithResult *functor, bigtime_t initialDelay, + bigtime_t idleTime, bigtime_t heartBeat = 1000000); + // after initialDelay starts looking for a slot when the system is + // idle for at least idleTime + +protected: + void AddTask(DelayedTask *); + void RemoveTask(DelayedTask *); + + bool Pulse(); + // return true if quitting + bigtime_t LatestRunTime() const; + + virtual bool KeepPulsingWhenEmpty() const = 0; + virtual void StartPulsingIfNeeded() = 0; + + BLocker fLock; + BObjectList fTaskList; + bigtime_t fHeartBeat; +}; + +class StandAloneTaskLoop : public TaskLoop { + // this task loop can work on it's own, just instantiate it + // and use it; It has to start it's own thread +public: + StandAloneTaskLoop(bool keepThread, bigtime_t heartBeat = 400000); + ~StandAloneTaskLoop(); + +protected: + void AddTask(DelayedTask *); + +private: + static status_t RunBinder(void *); + void Run(); + + virtual bool KeepPulsingWhenEmpty() const; + virtual void StartPulsingIfNeeded(); + + volatile bool fNeedToQuit; + volatile thread_id fScanThread; + bool fKeepThread; + + typedef TaskLoop _inherited; +}; + +class PiggybackTaskLoop : public TaskLoop { + // this TaskLoop needs periodic calls from a viewable's Pulse + // or some similar pulsing mechanism + // it does not have to need it's own thread, instead it uses an existing + // thread of a looper, etc. +public: + PiggybackTaskLoop(bigtime_t heartBeat = 100000); + ~PiggybackTaskLoop(); + virtual void PulseMe(); +private: + virtual bool KeepPulsingWhenEmpty() const; + virtual void StartPulsingIfNeeded(); + + bigtime_t fNextHeartBeatTime; + bool fPulseMe; +}; + + +inline bigtime_t +DelayedTask::RunAfterTime() const +{ + return fRunAfter; +} + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/TemplatesMenu.cpp b/src/kits/tracker/TemplatesMenu.cpp new file mode 100644 index 0000000000..1cd65d369b --- /dev/null +++ b/src/kits/tracker/TemplatesMenu.cpp @@ -0,0 +1,181 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Commands.h" + +#include "TemplatesMenu.h" +#include "IconMenuItem.h" +#include "MimeTypes.h" + +namespace BPrivate { + +const char *kTemplatesDirectory = "Tracker/Tracker New Templates"; +const char *kTemplatesMenuName = "New"; + +static const char *kOpenTemplatesMenuName = "Edit Templates"B_UTF8_ELLIPSIS; + +} + +TemplatesMenu::TemplatesMenu(const BMessenger &target, const char *label) + : BMenu(label), + fTarget(target), + fOpenItem(NULL) +{ +} + +TemplatesMenu::~TemplatesMenu() +{ +} + +void +TemplatesMenu::AttachedToWindow() +{ + BuildMenu(); + BMenu::AttachedToWindow(); + SetTargetForItems(fTarget); +} + +status_t +TemplatesMenu::SetTargetForItems(BHandler *target) +{ + status_t result = BMenu::SetTargetForItems(target); + if (fOpenItem) + fOpenItem->SetTarget(be_app_messenger); + return result; +} + +status_t +TemplatesMenu::SetTargetForItems(BMessenger messenger) +{ + status_t result = BMenu::SetTargetForItems(messenger); + if (fOpenItem) + fOpenItem->SetTarget(be_app_messenger); + return result; +} + +void +TemplatesMenu::UpdateMenuState() +{ + BuildMenu(false); +} + +bool +TemplatesMenu::BuildMenu(bool addItems) +{ + // Clear everything... + fOpenItem = NULL; + int32 count = CountItems(); + while (count--) + delete RemoveItem(0L); + + // Add the Folder + IconMenuItem *menuItem = new IconMenuItem("New Folder", new BMessage(kNewFolder), + B_DIR_MIMETYPE,B_MINI_ICON); + AddItem(menuItem); + menuItem->SetShortcut('N', 0); + + // The Templates folder + BPath path; + find_directory (B_USER_SETTINGS_DIRECTORY, &path, true); + path.Append(kTemplatesDirectory); + mkdir(path.Path(), 0777); + + count = 0; + + BEntry entry; + BDirectory templatesDir(path.Path()); + while (templatesDir.GetNextEntry(&entry) == B_OK) { + BNode node(&entry); + BNodeInfo nodeInfo(&node); + char fileName[B_FILE_NAME_LENGTH]; + entry.GetName(fileName); + if (nodeInfo.InitCheck() == B_OK) { + char mimeType[B_MIME_TYPE_LENGTH]; + nodeInfo.GetType(mimeType); + + BMimeType mime(mimeType); + if (mime.IsValid()) { + + if (count == 0) + AddSeparatorItem(); + + count++; + + // If not adding items, we are just seeing if there + // are any to list. So if we find one, immediately + // bail and return the result. + if (!addItems) + break; + + entry_ref ref; + entry.GetRef(&ref); + + BMessage *message = new BMessage(kNewEntryFromTemplate); + message->AddRef("refs_template", &ref); + message->AddString("name", fileName); + AddItem(new IconMenuItem(fileName, message, &nodeInfo, B_MINI_ICON)); + } + + } + } + + AddSeparatorItem(); + + // This is the message sent to open the templates folder. + BMessage *message = new BMessage(B_REFS_RECEIVED); + entry_ref dirRef; + if (templatesDir.GetEntry(&entry) == B_OK) + entry.GetRef(&dirRef); + message->AddRef("refs", &dirRef); + + // Add item to show templates folder. + fOpenItem = new BMenuItem(kOpenTemplatesMenuName, message); + AddItem(fOpenItem); + if (dirRef == entry_ref()) + fOpenItem->SetEnabled(false); + + return count > 0; +} diff --git a/src/kits/tracker/TemplatesMenu.h b/src/kits/tracker/TemplatesMenu.h new file mode 100644 index 0000000000..54b857a1cc --- /dev/null +++ b/src/kits/tracker/TemplatesMenu.h @@ -0,0 +1,71 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef __TEMPLATES_MENU__ +#define __TEMPLATES_MENU__ + + +#include + +namespace BPrivate { + +extern const char* kTemplatesDirectory; +extern const char* kTemplatesMenuName; + +class TemplatesMenu : public BMenu { +public: + TemplatesMenu(const BMessenger &target, + const char *label = kTemplatesMenuName); + virtual ~TemplatesMenu(); + + + virtual void AttachedToWindow(); + + virtual status_t SetTargetForItems(BHandler *); + virtual status_t SetTargetForItems(BMessenger); + + void UpdateMenuState(); + +private: + bool BuildMenu(bool addItems = true); + + BMessenger fTarget; + BMenuItem *fOpenItem; +}; + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/Tests.cpp b/src/kits/tracker/Tests.cpp new file mode 100644 index 0000000000..667af0648a --- /dev/null +++ b/src/kits/tracker/Tests.cpp @@ -0,0 +1,319 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#if DEBUG + +#include "Tests.h" + +#include +#include +#include +#include +#include + + +#include "EntryIterator.h" +#include "IconCache.h" +#include "Model.h" +#include "NodeWalker.h" +#include "StopWatch.h" +#include "Thread.h" + + + + +const char *pathsToSearch[] = { +// "/boot/home/config/settings/NetPositive/Bookmarks/", + "/boot/beos", + "/boot/apps", + "/boot/home", + 0 +}; + +namespace BTrackerPrivate { + +class IconSpewer : public SimpleThread { +public: + IconSpewer(bool newCache = true); + ~IconSpewer(); + void SetTarget(BWindow *target) + { this->target = target; } + + void Quit(); + void Run(); + +protected: + void DrawSomeNew(); + void DrawSomeOld(); + const entry_ref *NextRef(); +private: + BLocker locker; + bool quitting; + BWindow *target; + TNodeWalker *walker; + CachedEntryIterator *cachingIterator; + int32 searchPathIndex; + bigtime_t cycleTime; + bigtime_t lastCycleLap; + int32 numDrawn; + BStopWatch watch; + bool newCache; + BPath currentPath; + + entry_ref ref; +}; + +class IconTestWindow : public BWindow { +public: + IconTestWindow(); + bool QuitRequested(); +private: + IconSpewer iconSpewer; +}; + +} // namespace BTrackerPrivate + + +IconSpewer::IconSpewer(bool newCache) + : quitting(false), + cachingIterator(0), + searchPathIndex(0), + cycleTime(0), + lastCycleLap(0), + numDrawn(0), + watch("", true), + newCache(newCache) +{ + walker = new TNodeWalker(pathsToSearch[searchPathIndex++]); + if (newCache) + cachingIterator = new CachedEntryIterator(walker, 40); +} + + +IconSpewer::~IconSpewer() +{ + delete walker; + delete cachingIterator; +} + +void +IconSpewer::Run() +{ + BStopWatch watch("", true); + for (;;) { + AutoLock lock(locker); + + if (!lock || quitting) + break; + + lock.Unlock(); + if (newCache) + DrawSomeNew(); + else + DrawSomeOld(); + } +} + +void +IconSpewer::Quit() +{ + kill_thread(fScanThread); + fScanThread = -1; +} + +const icon_size kIconSize = B_LARGE_ICON; +const int32 kRowCount = 10; +const int32 kColumnCount = 10; + +void +IconSpewer::DrawSomeNew() +{ + target->Lock(); + BView *view = target->FindView("iconView"); + ASSERT(view); + + BRect bounds(target->Bounds()); + view->SetHighColor(Color(255, 255, 255)); + view->FillRect(bounds); + + view->SetHighColor(Color(0, 0, 0)); + char buffer[256]; + if (cycleTime) { + sprintf(buffer, "last cycle time %Ld ms", cycleTime/1000); + view->DrawString(buffer, BPoint(20, bounds.bottom - 20)); + } + if (numDrawn) { + sprintf(buffer, "average draw time %Ld us per icon", watch.ElapsedTime() / numDrawn); + view->DrawString(buffer, BPoint(20, bounds.bottom - 30)); + } + sprintf(buffer, "directory: %s", currentPath.Path()); + view->DrawString(buffer, BPoint(20, bounds.bottom - 40)); + + target->Unlock(); + + for (int32 row = 0; row < kRowCount; row++) { + for (int32 column = 0; column < kColumnCount; column++) { + BEntry entry(NextRef()); + Model model(&entry, true); + + if (!target->Lock()) + return; + + if (model.IsDirectory()) + entry.GetPath(¤tPath); + + IconCache::sIconCache->Draw(&model, view, BPoint(column * (kIconSize + 2), + row * (kIconSize + 2)), kNormalIcon, kIconSize, true); + target->Unlock(); + numDrawn++; + } + } +} + +bool oldIconCacheInited = false; +void +IconSpewer::DrawSomeOld() +{ +#if 0 + if (!oldIconCacheInited) + BIconCache::InitIconCaches(); + + target->Lock(); + target->SetTitle("old cache"); + BView *view = target->FindView("iconView"); + ASSERT(view); + + BRect bounds(target->Bounds()); + view->SetHighColor(Color(255, 255, 255)); + view->FillRect(bounds); + + view->SetHighColor(Color(0, 0, 0)); + char buffer[256]; + if (cycleTime) { + sprintf(buffer, "last cycle time %Ld ms", cycleTime/1000); + view->DrawString(buffer, BPoint(20, bounds.bottom - 20)); + } + if (numDrawn) { + sprintf(buffer, "average draw time %Ld us per icon", watch.ElapsedTime() / numDrawn); + view->DrawString(buffer, BPoint(20, bounds.bottom - 30)); + } + sprintf(buffer, "directory: %s", currentPath.Path()); + view->DrawString(buffer, BPoint(20, bounds.bottom - 40)); + + target->Unlock(); + + for (int32 row = 0; row < kRowCount; row++) { + for (int32 column = 0; column < kColumnCount; column++) { + BEntry entry(NextRef()); + BModel model(&entry, true); + + if (!target->Lock()) + return; + + if (model.IsDirectory()) + entry.GetPath(¤tPath); + + BIconCache::LockIconCache(); + BIconCache *iconCache = BIconCache::GetIconCache(&model, kIconSize); + iconCache->Draw(view, BPoint(column * (kIconSize + 2), + row * (kIconSize + 2)), B_NORMAL_ICON, kIconSize, true); + BIconCache::UnlockIconCache(); + + target->Unlock(); + numDrawn++; + } + } +#endif +} + +const entry_ref * +IconSpewer::NextRef() +{ + status_t result; + if (newCache) + result = cachingIterator->GetNextRef(&ref); + else + result = walker->GetNextRef(&ref); + + if (result == B_OK) + return &ref; + + delete walker; + if (!pathsToSearch[searchPathIndex]) { + bigtime_t now = watch.ElapsedTime(); + cycleTime = now - lastCycleLap; + lastCycleLap = now; + PRINT(("**************************hit end of disk, starting over\n")); + searchPathIndex = 0; + } + + walker = new TNodeWalker(pathsToSearch[searchPathIndex++]); + if (newCache) { + cachingIterator->SetTo(walker); + result = cachingIterator->GetNextRef(&ref); + } else + result = walker->GetNextRef(&ref); + + ASSERT(result == B_OK); + // we don't expect and cannot deal with any problems here + return &ref; +} + + + +IconTestWindow::IconTestWindow() + : BWindow(BRect(100, 100, 500, 600), "icon cache test", B_TITLED_WINDOW_LOOK, + B_NORMAL_WINDOW_FEEL, 0), + iconSpewer(modifiers() == 0) +{ + iconSpewer.SetTarget(this); + BView *view = new BView(Bounds(), "iconView", B_FOLLOW_ALL, B_WILL_DRAW); + AddChild(view); + iconSpewer.Go(); +} + +bool +IconTestWindow::QuitRequested() +{ + iconSpewer.Quit(); + return true; +} + +void +RunIconCacheTests() +{ + (new IconTestWindow())->Show(); +} + +#endif diff --git a/src/kits/tracker/Tests.h b/src/kits/tracker/Tests.h new file mode 100644 index 0000000000..caa9f1a22f --- /dev/null +++ b/src/kits/tracker/Tests.h @@ -0,0 +1,39 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#if DEBUG +void RunIconCacheTests(); +#else +inline void RunIconCacheTests() {} +#endif diff --git a/src/kits/tracker/TextWidget.cpp b/src/kits/tracker/TextWidget.cpp new file mode 100644 index 0000000000..9264381bc9 --- /dev/null +++ b/src/kits/tracker/TextWidget.cpp @@ -0,0 +1,524 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Attributes.h" +#include "ContainerWindow.h" +#include "Commands.h" +#include "FSUtils.h" +#include "PoseView.h" +#include "TextWidget.h" +#include "Utilities.h" +#include "WidgetAttributeText.h" + + +const float kWidthMargin = 20; + + +BTextWidget::BTextWidget(Model *model, BColumn *column, BPoseView *view) + : + fText(WidgetAttributeText::NewWidgetText(model, column, view)), + fAttrHash(column->AttrHash()), + fAlignment(column->Alignment()), + fEditable(column->Editable()), + fVisible(true), + fActive(false), + fSymLink(model->IsSymLink()) +{ +} + + +BTextWidget::~BTextWidget() +{ + delete fText; +} + + +int +BTextWidget::Compare(const BTextWidget &with, BPoseView *view) const +{ + return fText->Compare(*with.fText, view); +} + + +void +BTextWidget::RecalculateText(const BPoseView *view) +{ + fText->SetDirty(true); + fText->CheckViewChanged(view); +} + + +const char * +BTextWidget::Text() const +{ + StringAttributeText *textAttribute = dynamic_cast(fText); + + ASSERT(textAttribute); + if (!textAttribute) + return ""; + + return textAttribute->Value(); +} + + +float +BTextWidget::TextWidth(const BPoseView *pose) const +{ + return fText->Width(pose); +} + + +float +BTextWidget::PreferredWidth(const BPoseView *pose) const +{ + return fText->PreferredWidth(pose) + 1; +} + + +BRect +BTextWidget::ColumnRect(BPoint poseLoc, const BColumn *column, + const BPoseView *view) +{ + if (view->ViewMode() != kListMode) { + // ColumnRect only makes sense in list view, return + // CalcRect otherwise + return CalcRect(poseLoc, column, view); + } + BRect result; + result.left = column->Offset() + poseLoc.x; + result.right = result.left + column->Width(); + result.bottom = poseLoc.y + view->ListElemHeight() - 1; + result.top = result.bottom - view->FontHeight(); + return result; +} + + +BRect +BTextWidget::CalcRectCommon(BPoint poseLoc, const BColumn *column, + const BPoseView *view, float textWidth) +{ + BRect result; + if (view->ViewMode() == kListMode) { + poseLoc.x += column->Offset(); + + switch (fAlignment) { + case B_ALIGN_LEFT: + result.left = poseLoc.x; + result.right = result.left + textWidth + 1; + break; + + case B_ALIGN_CENTER: + result.left = poseLoc.x + (column->Width() / 2) - (textWidth / 2); + if (result.left < 0) + result.left = 0; + result.right = result.left + textWidth + 1; + break; + + case B_ALIGN_RIGHT: + result.right = poseLoc.x + column->Width(); + result.left = result.right - textWidth - 1; + if (result.left < 0) + result.left = 0; + break; + default: + TRESPASS(); + } + + result.bottom = poseLoc.y + (view->ListElemHeight() - 1); + } else { + if (view->ViewMode() == kIconMode) + result.left = poseLoc.x + (B_LARGE_ICON - textWidth) / 2; + else + // MINI_ICON_MODE rect calc + result.left = poseLoc.x + B_MINI_ICON + kMiniIconSeparator; + + result.right = result.left + textWidth; + result.bottom = poseLoc.y + view->IconPoseHeight(); + + } + result.top = result.bottom - view->FontHeight(); + + return result; +} + + +BRect +BTextWidget::CalcRect(BPoint poseLoc, const BColumn *column, + const BPoseView *view) +{ + return CalcRectCommon(poseLoc, column, view, fText->Width(view)); +} + + +BRect +BTextWidget::CalcOldRect(BPoint poseLoc, const BColumn *column, + const BPoseView *view) +{ + return CalcRectCommon(poseLoc, column, view, fText->CurrentWidth()); +} + + +BRect +BTextWidget::CalcClickRect(BPoint poseLoc, const BColumn *column, + const BPoseView* view) +{ + BRect result = CalcRect(poseLoc, column, view); + if (result.Width() < kWidthMargin) { + // if resulting rect too narrow, make it a bit wider + // for comfortable clicking + if (column && column->Width() < kWidthMargin) + result.right = result.left + column->Width(); + else + result.right = result.left + kWidthMargin; + } + return result; +} + + +void +BTextWidget::MouseUp(BRect bounds, BPoseView *view, BPose *pose, BPoint, + bool delayedEdit) +{ + // wait until a double click time to see if we are double clicking + // or selecting widget for editing + // start editing early if mouse left widget or modifier down + + if (!IsEditable()) + return; + + if (delayedEdit) { + bigtime_t doubleClickTime; + get_click_speed(&doubleClickTime); + doubleClickTime += system_time(); + + while (system_time() < doubleClickTime) { + // loop for double-click time and watch the mouse and keyboard + + BPoint point; + uint32 buttons; + view->GetMouse(&point, &buttons, false); + if (buttons) + // if mouse button goes down then a double click, exit + // without editing + return; + + if (!bounds.Contains(point)) + // mouse has moved outside of text widget so go into edit mode + break; + + if (modifiers() & (B_SHIFT_KEY | B_COMMAND_KEY | B_CONTROL_KEY | B_MENU_KEY)) + // watch the keyboard (ignoring standard locking keys) + break; + + snooze(100000); + } + } + + StartEdit(bounds, view, pose); +} + + +static filter_result +TextViewFilter(BMessage *message, BHandler **, BMessageFilter *filter) +{ + uchar key; + if (message->FindInt8("byte", (int8 *)&key) != B_OK) + return B_DISPATCH_MESSAGE; + + BPoseView *poseView = dynamic_cast(filter->Looper())-> + PoseView(); + + if (key == B_RETURN || key == B_ESCAPE) { + poseView->CommitActivePose(key == B_RETURN); + return B_SKIP_MESSAGE; + } + + if (key == B_TAB) { + if (poseView->ActivePose()) { + if (message->FindInt32("modifiers") & B_SHIFT_KEY) + poseView->ActivePose()->EditPreviousWidget(poseView); + else + poseView->ActivePose()->EditNextWidget(poseView); + } + + return B_SKIP_MESSAGE; + } + + // the BTextView doesn't respect window borders when resizing itself; + // we try to work-around this "bug" here. + + // find the text editing view + BView *scrollView = poseView->FindView("BorderView"); + if (scrollView != NULL) { + BTextView *textView = dynamic_cast(scrollView->FindView("WidgetTextView")); + if (textView != NULL) { + BRect rect = scrollView->Frame(); + + if (rect.right + 3 > poseView->Bounds().right + || rect.left - 3 < 0) + textView->MakeResizable(true, NULL); + } + } + + return B_DISPATCH_MESSAGE; +} + + +void +BTextWidget::StartEdit(BRect bounds, BPoseView *view, BPose *pose) +{ + if (!IsEditable()) + return; + + // don't allow editing of the trash directory name + BEntry entry(pose->TargetModel()->EntryRef()); + if (entry.InitCheck() == B_OK && FSIsTrashDir(&entry)) + return; + + // don't allow editing of the "Disks" icon name + if (pose->TargetModel()->IsRoot()) + return; + + if (!ConfirmChangeIfWellKnownDirectory(&entry, "rename")) + return; + + // get bounds with full text length + BRect rect(bounds); + BRect textRect(bounds); + rect.OffsetBy(-2, -1); + rect.right += 1; + + BFont font; + view->GetFont(&font); + BTextView *textView = new BTextView(rect, "WidgetTextView", textRect, &font, 0, + B_FOLLOW_ALL, B_WILL_DRAW); + + textView->SetWordWrap(false); + DisallowMetaKeys(textView); + fText->SetUpEditing(textView); + + textView->AddFilter(new BMessageFilter(B_KEY_DOWN, TextViewFilter)); + + rect.right = rect.left + textView->LineWidth() + 3; + // center new width, if necessary + if (view->ViewMode() == kIconMode + || view->ViewMode() == kListMode && fAlignment == B_ALIGN_CENTER) + rect.OffsetBy(bounds.Width() / 2 - rect.Width() / 2, 0); + + rect.bottom = rect.top + textView->LineHeight() + 1; + textRect = rect.OffsetToCopy(2, 1); + textRect.right -= 3; + textRect.bottom--; + textView->SetTextRect(textRect); + + textRect = view->Bounds(); + bool hitBorder = false; + if (rect.left < 1) + rect.left = 1, hitBorder = true; + if (rect.right > textRect.right) + rect.right = textRect.right - 2, hitBorder = true; + + textView->MoveTo(rect.LeftTop()); + textView->ResizeTo(rect.Width(), rect.Height()); + + BScrollView *scrollView = new BScrollView("BorderView", textView, 0, 0, false, + false, B_PLAIN_BORDER); + view->AddChild(scrollView); + + // configure text view + switch (view->ViewMode()) { + case kIconMode: + textView->SetAlignment(B_ALIGN_CENTER); + break; + + case kMiniIconMode: + textView->SetAlignment(B_ALIGN_LEFT); + break; + + case kListMode: + textView->SetAlignment(fAlignment); + break; + } + textView->MakeResizable(true, hitBorder ? NULL : scrollView); + + view->SetActivePose(pose); // tell view about pose + SetActive(true); // for widget + + textView->SelectAll(); + textView->MakeFocus(); + + // make this text widget invisible while we edit it + SetVisible(false); + + ASSERT(view->Window()); // how can I not have a Window here??? + + if (view->Window()) + // force immediate redraw so TextView appears instantly + view->Window()->UpdateIfNeeded(); +} + + +void +BTextWidget::StopEdit(bool saveChanges, BPoint poseLoc, BPoseView *view, + BPose *pose, int32 poseIndex) +{ + // find the text editing view + BView *scrollView = view->FindView("BorderView"); + ASSERT(scrollView); + if (!scrollView) + return; + + BTextView *textView = dynamic_cast(scrollView->FindView("WidgetTextView")); + ASSERT(textView); + if (!textView) + return; + + BColumn *column = view->ColumnFor(fAttrHash); + ASSERT(column); + if (!column) + return; + + if (saveChanges && fText->CommitEditedText(textView)) { + // we have an actual change, re-sort + view->CheckPoseSortOrder(pose, poseIndex); + } + + // make text widget visible again + SetVisible(true); + view->Invalidate(ColumnRect(poseLoc, column, view)); + + // force immediate redraw so TEView disappears + scrollView->RemoveSelf(); + delete scrollView; + + ASSERT(view->Window()); + view->Window()->UpdateIfNeeded(); + view->MakeFocus(); + + SetActive(false); +} + + +void +BTextWidget::CheckAndUpdate(BPoint loc, const BColumn *column, BPoseView *view) +{ + BRect oldRect; + if (view->ViewMode() != kListMode) + oldRect = CalcOldRect(loc, column, view); + + if (fText->CheckAttributeChanged() && fText->CheckViewChanged(view)) { + BRect invalRect(ColumnRect(loc, column, view)); + if (view->ViewMode() != kListMode) + invalRect = invalRect | oldRect; + view->Invalidate(invalRect); + } +} + + +void +BTextWidget::SelectAll(BPoseView *view) +{ + BTextView *text = dynamic_cast(view->FindView("WidgetTextView")); + if (text) + text->SelectAll(); +} + + +void +BTextWidget::Draw(BRect eraseRect, BRect textRect, float, BPoseView *view, + BView *drawView, bool selected, uint32 clipboardMode, BPoint offset, bool direct) +{ + if (direct) { + // erase area we're going to draw in + if (view->EraseWidgetTextBackground() || selected) { + drawView->SetDrawingMode(B_OP_COPY); + eraseRect.OffsetBy(offset); + drawView->FillRect(eraseRect, B_SOLID_LOW); + } else + drawView->SetDrawingMode(B_OP_OVER); + + // set high color + rgb_color highColor; + if (view->IsDesktopWindow()) { + if (selected) + highColor = kWhite; + else + highColor = view->DeskTextColor(); + } else if (selected && view->Window()->IsActive() && !view->EraseWidgetTextBackground()) { + highColor = kWhite; + } else + highColor = kBlack; + + if (clipboardMode == kMoveSelectionTo && !selected) { + view->SetDrawingMode(B_OP_ALPHA); + view->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY); + highColor.alpha = 64; + } + drawView->SetHighColor(highColor); + } + + BPoint loc; + textRect.OffsetBy(offset); + + loc.y = textRect.bottom - view->FontInfo().descent; + loc.x = textRect.left + 1; + + drawView->MovePenTo(loc); + drawView->DrawString(fText->FittingText(view)); + + if (fSymLink && (fAttrHash == view->FirstColumn()->AttrHash())) { + // ToDo: + // this should be exported to the WidgetAttribute class, probably + // by having a per widget kind style + if (direct) + drawView->SetHighColor(125, 125, 125); + + textRect.right = textRect.left + fText->Width(view); + // only underline text part + drawView->StrokeLine(textRect.LeftBottom(), textRect.RightBottom(), + B_MIXED_COLORS); + } +} diff --git a/src/kits/tracker/TextWidget.h b/src/kits/tracker/TextWidget.h new file mode 100644 index 0000000000..2243a72d99 --- /dev/null +++ b/src/kits/tracker/TextWidget.h @@ -0,0 +1,165 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef _TEXT_WIDGET_H +#define _TEXT_WIDGET_H + +#include "Model.h" +#include "WidgetAttributeText.h" + +namespace BPrivate { + +class BPose; +class BPoseView; +class BColumn; + +class BTextWidget { +public: + BTextWidget(Model *, BColumn *, BPoseView *); + virtual ~BTextWidget(); + + void Draw(BRect widgetRect, BRect widgetTextRect, float width, BPoseView *, + bool selected, uint32 clipboardMode); + void Draw(BRect widgetRect, BRect widgetTextRect, float width, BPoseView *, + BView *drawView, bool selected, uint32 clipboardMode, BPoint offset, bool direct); + // second call is used for offscreen drawing, where PoseView + // and current drawing view are different + + void MouseUp(BRect bounds, BPoseView *, BPose *, BPoint mouseLoc, + bool delayedEdit); + BRect CalcRect(BPoint poseLoc, const BColumn *, const BPoseView *); + // returns the rect derived from the formatted string width + // may force WidgetAttributeText recalculation + BRect CalcClickRect(BPoint poseLoc, const BColumn *, const BPoseView *); + // calls CalcRect, if result too narow, returns a wider rect for + // easy clicking + BRect ColumnRect(BPoint poseLoc, const BColumn *, const BPoseView *); + // returns the rect of the widget in a column, regardless + // of the string width; faster than CalcRect + BRect CalcOldRect(BPoint poseLoc, const BColumn *, const BPoseView *); + // after an update call this to determine the old rect so that + // we can invalidate properly + + void StartEdit(BRect bounds, BPoseView *, BPose *); + void StopEdit(bool saveChanges, BPoint loc, BPoseView *, BPose *, int32 index); + + void SelectAll(BPoseView *view); + void CheckAndUpdate(BPoint, const BColumn *, BPoseView *); + + uint32 AttrHash() const; + bool IsEditable() const; + void SetEditable(bool); + bool IsVisible() const; + void SetVisible(bool); + bool IsActive() const; + void SetActive(bool); + + const char *Text() const; + // returns the untrucated version of the text + float TextWidth(const BPoseView *) const; + float PreferredWidth(const BPoseView *) const; + int Compare(const BTextWidget &, BPoseView *) const; + // used for sorting in PoseViews + + void RecalculateText(const BPoseView *view); + +private: + BRect CalcRectCommon(BPoint poseLoc, const BColumn *, const BPoseView *, float width); + + WidgetAttributeText *fText; + uint32 fAttrHash; // ToDo: get rid of this + alignment fAlignment; + + bool fEditable : 1; + bool fVisible : 1; + bool fActive : 1; + bool fSymLink : 1; +}; + +inline uint32 +BTextWidget::AttrHash() const +{ + return fAttrHash; +} + +inline void +BTextWidget::SetEditable(bool on) +{ + fEditable = on; +} + +inline bool +BTextWidget::IsEditable() const +{ + return fEditable && fText->IsEditable(); +} + +inline bool +BTextWidget::IsVisible() const +{ + return fVisible; +} + +inline void +BTextWidget::SetVisible(bool on) +{ + fVisible = on; +} + +inline bool +BTextWidget::IsActive() const +{ + return fActive; +} + +inline void +BTextWidget::SetActive(bool on) +{ + fActive = on; +} + + +inline void +BTextWidget::Draw(BRect widgetRect, BRect widgetTextRect, float width, + BPoseView *view, bool selected, uint32 clipboardMode) +{ + Draw(widgetRect, widgetTextRect, width, view, (BView *)view, selected, + clipboardMode, BPoint(0, 0), true); +} + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/Thread.cpp b/src/kits/tracker/Thread.cpp new file mode 100644 index 0000000000..c5caee4e2b --- /dev/null +++ b/src/kits/tracker/Thread.cpp @@ -0,0 +1,135 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include "Thread.h" +#include "FunctionObject.h" + +SimpleThread::SimpleThread(int32 priority, const char *name) + : fScanThread(-1), + fPriority(priority), + fName(name) +{ +} + + +SimpleThread::~SimpleThread() +{ + if (fScanThread > 0 && fScanThread != find_thread(NULL)) + // kill the thread if it is not the one we are running in + kill_thread(fScanThread); +} + +void +SimpleThread::Go() +{ + fScanThread = spawn_thread(SimpleThread::RunBinder, fName ? fName : "TrackerTaskLoop", + fPriority, this); + resume_thread(fScanThread); +} + +status_t +SimpleThread::RunBinder(void *castToThis) +{ + SimpleThread *self = static_cast(castToThis); + self->Run(); + return B_OK; +} + +void +Thread::Launch(FunctionObject *functor, int32 priority, const char *name) +{ + new Thread(functor, priority, name); +} + + +Thread::Thread(FunctionObject *functor, int32 priority, const char *name) + : SimpleThread(priority, name), + fFunctor(functor) +{ + Go(); +} + +Thread::~Thread() +{ + delete fFunctor; +} + + +void +Thread::Run() +{ + (*fFunctor)(); + delete this; + // commit suicide +} + +void +ThreadSequence::Launch(BObjectList *list, bool async, int32 priority) +{ + if (!async) + // if not async, don't even create a thread, just do it right away + Run(list); + else + new ThreadSequence(list, priority); +} + + +ThreadSequence::ThreadSequence(BObjectList *list, int32 priority) + : SimpleThread(priority), + fFunctorList(list) +{ + Go(); +} + + +ThreadSequence::~ThreadSequence() +{ + delete fFunctorList; +} + +void +ThreadSequence::Run(BObjectList *list) +{ + int32 count = list->CountItems(); + for (int32 index = 0; index < count; index++) + (*list->ItemAt(index))(); +} + +void +ThreadSequence::Run() +{ + Run(fFunctorList); + delete this; + // commit suicide +} diff --git a/src/kits/tracker/Thread.h b/src/kits/tracker/Thread.h new file mode 100644 index 0000000000..81634abff4 --- /dev/null +++ b/src/kits/tracker/Thread.h @@ -0,0 +1,370 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef __THREAD__ +#define __THREAD__ + +#include +#include "ObjectList.h" +#include "FunctionObject.h" +#include "Utilities.h" + +namespace BPrivate { + +class SimpleThread { + // this should only be used as a base class, + // subclass needs to add proper locking mechanism +public: + SimpleThread(int32 priority = B_LOW_PRIORITY, const char *name = 0); + virtual ~SimpleThread(); + + void Go(); + +private: + static status_t RunBinder(void *); + virtual void Run() = 0; + +protected: + thread_id fScanThread; + int32 fPriority; + const char *fName; +}; + + +class Thread : private SimpleThread { +public: + static void Launch(FunctionObject *functor, + int32 priority = B_LOW_PRIORITY, const char *name = 0); + +private: + Thread(FunctionObject *, int32 priority, const char *name); + ~Thread(); + virtual void Run(); + + FunctionObject *fFunctor; +}; + +class ThreadSequence : private SimpleThread { +public: + static void Launch(BObjectList *, bool async = true, + int32 priority = B_LOW_PRIORITY); + +private: + ThreadSequence(BObjectList *, int32 priority); + ~ThreadSequence(); + + virtual void Run(); + static void Run(BObjectList *list); + + BObjectList *fFunctorList; +}; + +// would use SingleParamFunctionObjectWithResult, except mwcc won't handle this +template +class SingleParamFunctionObjectWorkaround : public FunctionObjectWithResult { +public: + SingleParamFunctionObjectWorkaround(status_t (*function)(Param1), Param1 param1) + : fFunction(function), + fParam1(param1) + { + } + + + virtual void operator()() + { (fFunction)(fParam1); } + + virtual ulong Size() const { return sizeof(*this); } + +private: + status_t (*fFunction)(Param1); + Param1 fParam1; +}; + +template +class SimpleMemberFunctionObjectWorkaround : public FunctionObjectWithResult { +public: + SimpleMemberFunctionObjectWorkaround(status_t (T::*function)(), T *onThis) + : fFunction(function), + fOnThis(onThis) + { + } + + + virtual void operator()() + { (fOnThis->*fFunction)(); } + + virtual ulong Size() const { return sizeof(*this); } + +private: + status_t (T::*fFunction)(); + T fOnThis; +}; + +template +class TwoParamFunctionObjectWorkaround : public FunctionObjectWithResult { +public: + TwoParamFunctionObjectWorkaround(status_t (*callThis)(Param1, Param2), + Param1 param1, Param2 param2) + : function(callThis), + fParam1(param1), + fParam2(param2) + { + } + + virtual void operator()() + { (function)(fParam1, fParam2); } + + virtual uint32 Size() const { return sizeof(*this); } + +private: + status_t (*function)(Param1, Param2); + Param1 fParam1; + Param2 fParam2; +}; + +template +class ThreeParamFunctionObjectWorkaround : public FunctionObjectWithResult { +public: + ThreeParamFunctionObjectWorkaround(status_t (*callThis)(Param1, Param2, Param3), + Param1 param1, Param2 param2, Param3 param3) + : function(callThis), + fParam1(param1), + fParam2(param2), + fParam3(param3) + { + } + + virtual void operator()() + { (function)(fParam1, fParam2, fParam3); } + + virtual uint32 Size() const { return sizeof(*this); } + +private: + status_t (*function)(Param1, Param2, Param3); + Param1 fParam1; + Param2 fParam2; + Param3 fParam3; +}; + +template +class FourParamFunctionObjectWorkaround : public FunctionObjectWithResult { +public: + FourParamFunctionObjectWorkaround(status_t (*callThis)(Param1, Param2, Param3, Param4), + Param1 param1, Param2 param2, Param3 param3, Param4 param4) + : function(callThis), + fParam1(param1), + fParam2(param2), + fParam3(param3), + fParam4(param4) + { + } + + virtual void operator()() + { (function)(fParam1, fParam2, fParam3, fParam4); } + + virtual uint32 Size() const { return sizeof(*this); } + +private: + status_t (*function)(Param1, Param2, Param3, Param4); + Param1 fParam1; + Param2 fParam2; + Param3 fParam3; + Param4 fParam4; +}; + +template +void +LaunchInNewThread(const char *name, int32 priority, status_t (*func)(Param1), Param1 p1) +{ + Thread::Launch(new SingleParamFunctionObjectWorkaround(func, p1), + priority, name); +} + +template +void +LaunchInNewThread(const char *name, int32 priority, status_t (T::*function)(), T *onThis) +{ + Thread::Launch(new SimpleMemberFunctionObjectWorkaround(func, onThis), + priority, name); +} + +template +void +LaunchInNewThread(const char *name, int32 priority, + status_t (*func)(Param1, Param2), + Param1 p1, Param2 p2) +{ + Thread::Launch(new TwoParamFunctionObjectWorkaround(func, p1, p2), + priority, name); +} + +template +void +LaunchInNewThread(const char *name, int32 priority, + status_t (*func)(Param1, Param2, Param3), + Param1 p1, Param2 p2, Param3 p3) +{ + Thread::Launch(new ThreeParamFunctionObjectWorkaround(func, p1, p2, p3), priority, name); +} + +template +void +LaunchInNewThread(const char *name, int32 priority, + status_t (*func)(Param1, Param2, Param3, Param4), + Param1 p1, Param2 p2, Param3 p3, Param4 p4) +{ + Thread::Launch(new FourParamFunctionObjectWorkaround(func, p1, p2, p3, p4), priority, name); +} + +template +class MouseDownThread { +public: + static void TrackMouse(View *view, void (View::*)(BPoint), + void (View::*)(BPoint, uint32) = 0, bigtime_t pressingPeriod = 100000); + +protected: + MouseDownThread(View *view, void (View::*)(BPoint), + void (View::*)(BPoint, uint32), bigtime_t pressingPeriod); + + virtual ~MouseDownThread(); + + void Go(); + virtual void Track(); + + static status_t TrackBinder(void *); +private: + + BMessenger fOwner; + void (View::*fDonePressing)(BPoint); + void (View::*fPressing)(BPoint, uint32); + bigtime_t fPressingPeriod; + volatile thread_id fThreadID; +}; + + +template +void +MouseDownThread::TrackMouse(View *view, + void(View::*donePressing)(BPoint), + void(View::*pressing)(BPoint, uint32), bigtime_t pressingPeriod) +{ + (new MouseDownThread(view, donePressing, pressing, pressingPeriod))->Go(); +} + + +template +MouseDownThread::MouseDownThread(View *view, + void (View::*donePressing)(BPoint), + void (View::*pressing)(BPoint, uint32), bigtime_t pressingPeriod) + : fOwner(view, view->Window()), + fDonePressing(donePressing), + fPressing(pressing), + fPressingPeriod(pressingPeriod) +{ +} + + +template +MouseDownThread::~MouseDownThread() +{ + if (fThreadID > 0) { + kill_thread(fThreadID); + // dead at this point + TRESPASS(); + } +} + + +template +void +MouseDownThread::Go() +{ + fThreadID = spawn_thread(&MouseDownThread::TrackBinder, "MouseTrackingThread", + B_NORMAL_PRIORITY, this); + + if (fThreadID <= 0 || resume_thread(fThreadID) != B_OK) + // didn't start, don't leak self + delete this; +} + +template +status_t +MouseDownThread::TrackBinder(void *castToThis) +{ + MouseDownThread *self = static_cast(castToThis); + self->Track(); + // dead at this point + TRESPASS(); + return B_OK; +} + +template +void +MouseDownThread::Track() +{ + for (;;) { + MessengerAutoLocker lock(&fOwner); + if (!lock) + break; + + BLooper *looper; + View *view = dynamic_cast(fOwner.Target(&looper)); + if (!view) + break; + + uint32 buttons; + BPoint location; + view->GetMouse(&location, &buttons, false); + if (!buttons) { + (view->*fDonePressing)(location); + break; + } + if (fPressing) + (view->*fPressing)(location, buttons); + + lock.Unlock(); + snooze(fPressingPeriod); + } + + delete this; + ASSERT(!"should not be here"); +} + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/TitleView.cpp b/src/kits/tracker/TitleView.cpp new file mode 100644 index 0000000000..2349b8ca10 --- /dev/null +++ b/src/kits/tracker/TitleView.cpp @@ -0,0 +1,767 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// ListView title drawing and mouse manipulation classes +#include "TitleView.h" + +#include +#include +#include +#include +#include + +#include + +#include "Commands.h" +#include "ContainerWindow.h" +#include "PoseView.h" +#include "Utilities.h" + +const rgb_color kTitleBackground = {220, 220, 220, 255}; +const rgb_color kDarkTitleBackground = {180, 180, 180, 255}; +const rgb_color kHighlightColor = {100, 100, 210, 255}; +const rgb_color kLightGray = {150, 150, 150, 255}; +const rgb_color kGray = {100, 100, 100, 255}; +const rgb_color kDarkGray = {70, 70, 70, 255}; + +const unsigned char kHorizontalResizeCursor[] = { + 16, 1, 7, 7, + 0, 0, 1, 0, 1, 0, 1, 0, 9, 32, 25, 48, 57, 56, 121, 60, + 57, 56, 25, 48, 9, 32, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, + 3, 128, 3, 128, 3, 128, 15, 224, 31, 240, 63, 248, 127, 252, 255, 254, + 127, 252, 63, 248, 31, 240, 15, 224, 3, 128, 3, 128, 3, 128, 0, 0 +}; + + + +BTitleView::BTitleView(BRect frame, BPoseView *view) + : BView(frame, "TitleView", B_FOLLOW_LEFT_RIGHT, B_WILL_DRAW), + fPoseView(view), + fTitleList(10, true), + fHorizontalResizeCursor(kHorizontalResizeCursor), + fPreviouslyClickedColumnTitle(0) + +{ + SetHighColor(kTitleBackground); + SetLowColor(kTitleBackground); + SetViewColor(kTitleBackground); + + BFont font(be_plain_font); + font.SetSize(9); + SetFont(&font); + + Reset(); +} + +BTitleView::~BTitleView() +{ +} + +void +BTitleView::Reset() +{ + fTitleList.MakeEmpty(); + for (int32 index = 0; ; index++) { + BColumn *column = fPoseView->ColumnAt(index); + if (!column) + break; + fTitleList.AddItem(new BColumnTitle(this, column)); + } +} + +void +BTitleView::AddTitle(BColumn *column, const BColumn *after) +{ + int32 count = fTitleList.CountItems(); + int32 index; + if (after) + for (index = 0; index < count; index++) { + BColumn *titleColumn = fTitleList.ItemAt(index)->Column(); + + if (after == titleColumn) { + index++; + break; + } + } + else + index = count; + + fTitleList.AddItem(new BColumnTitle(this, column), index); + Invalidate(); +} + +void +BTitleView::RemoveTitle(BColumn *column) +{ + int32 count = fTitleList.CountItems(); + for (int32 index = 0; index < count; index++) { + BColumnTitle *title = fTitleList.ItemAt(index); + if (title->Column() == column) { + fTitleList.RemoveItem(title); + break; + } + } + + Invalidate(); +} + +void +BTitleView::Draw(BRect rect) +{ + Draw(rect, false); +} + +void +BTitleView::Draw(BRect, bool useOffscreen, bool updateOnly, + const BColumnTitle *pressedColumn, + void (*trackRectBlitter)(BView *, BRect), BRect passThru) +{ + BRect bounds(Bounds()); + + BView *view; + + if (useOffscreen) { + ASSERT(offscreen); + BRect frame(bounds); + frame.right += frame.left; + // this is kind of messy way of avoiding being clipped by the ammount the + // title is scrolled to the left + // ToDo: + // fix this + view = offscreen->BeginUsing(frame); + view->SetOrigin(-bounds.left, 0); + view->SetLowColor(LowColor()); + view->SetHighColor(HighColor()); + BFont font(be_plain_font); + font.SetSize(9); + view->SetFont(&font); + } else + view = this; + + // fill background with light gray background + if (!updateOnly) + view->FillRect(bounds, B_SOLID_LOW); + + view->BeginLineArray(4); + view->AddLine(bounds.LeftTop(), bounds.RightTop(), kGray); + view->AddLine(bounds.LeftBottom(), bounds.RightBottom(), kGray); + // draw lighter gray and white inset lines + bounds.InsetBy(0, 1); + view->AddLine(bounds.LeftBottom(), bounds.RightBottom(), kLightGray); + view->AddLine(bounds.LeftTop(), bounds.RightTop(), kWhite); + view->EndLineArray(); + + int32 count = fTitleList.CountItems(); + float minx = bounds.right; + float maxx = bounds.left; + for (int32 index = 0; index < count; index++) { + BColumnTitle *title = fTitleList.ItemAt(index); + title->Draw(view, title == pressedColumn); + BRect titleBounds(title->Bounds()); + if (titleBounds.left < minx) + minx = titleBounds.left; + if (titleBounds.right > maxx) + maxx = titleBounds.right; + } + + // first and last shades before and after first column + BRect tmp(bounds); + tmp.right = maxx; + tmp.left = minx; + tmp.InsetBy(-1, 0); + view->BeginLineArray(2); + view->AddLine(tmp.LeftTop(), tmp.LeftBottom(), kGray); + view->AddLine(tmp.RightTop(), tmp.RightBottom(), kWhite); + view->EndLineArray(); + + if (useOffscreen) { + if (trackRectBlitter) + (trackRectBlitter)(view, passThru); + view->Sync(); + DrawBitmap(offscreen->Bitmap()); + offscreen->DoneUsing(); + } else if (trackRectBlitter) + (trackRectBlitter)(view, passThru); +} + +void +BTitleView::MouseDown(BPoint where) +{ + if (!Window()->IsActive()) { + // wasn't active, just activate and bail + Window()->Activate(); + return; + } + + // finish any pending edits + fPoseView->CommitActivePose(); + + BColumnTitle *title = FindColumnTitle(where); + BColumnTitle *resizedTitle = InColumnResizeArea(where); + + uint32 buttons; + GetMouse(&where, &buttons); + + // Check if the user clicked the secondary mouse button. + // if so, display the attribute menu: + + if (buttons & B_SECONDARY_MOUSE_BUTTON) { + BContainerWindow *window = dynamic_cast + (Window()); + BPopUpMenu *menu = new BPopUpMenu("Attributes", false, false); + menu->SetFont(be_plain_font); + window->NewAttributeMenu(menu); + window->AddMimeTypesToMenu(menu); + window->MarkAttributeMenu(menu); + menu->SetTargetForItems(window->PoseView()); + menu->Go(ConvertToScreen(where), true, false); + return; + } + + bigtime_t doubleClickSpeed; + get_click_speed(&doubleClickSpeed); + + if (resizedTitle) { + bool force = static_cast(buttons & B_TERTIARY_MOUSE_BUTTON); + if (force || buttons & B_PRIMARY_MOUSE_BUTTON) { + + if (force || fPreviouslyClickedColumnTitle != 0) { + if (force || system_time() - fPreviousLeftClickTime < doubleClickSpeed) { + if (fPoseView->ResizeColumnToWidest(resizedTitle->Column())) { + Invalidate(); + return; + } + } + } + fPreviousLeftClickTime = system_time(); + fPreviouslyClickedColumnTitle = resizedTitle; + } + } else if (!title) + return; + + ColumnTrackState *trackState; + if (resizedTitle) + trackState = new ColumnResizeState(this, resizedTitle, where); + else + trackState = new ColumnDragState(this, title, where); + + // track the mouse + // - if it is pressed shortly and not moved, it is a click + // all else is a track + bigtime_t pastClickTime = system_time() + doubleClickSpeed; + bool pastClick = false; + for (;;) { + BPoint old(where); + + GetMouse(&where, &buttons); + + if (!buttons) { + if (!pastClick) + trackState->Clicked(where, buttons); + else + trackState->Done(where); + break; + } + + BRect oldMarging(old, old); + oldMarging.InsetBy(-1, -1); + + if ((pastClick && where != old) || !oldMarging.Contains(where)) { + // if not pressing yet, use a margin to start, else + // call moved on any mouse movement + pastClick = true; + trackState->MouseMoved(where, buttons); + } + if (!pastClick && system_time() > pastClickTime) + pastClick = true; + + + snooze(15000); + } + + delete trackState; +} + +void +BTitleView::MouseMoved(BPoint where, uint32 code, const BMessage *message) +{ + switch (code) { + default: + if (InColumnResizeArea(where) && Window()->IsActive()) + SetViewCursor(&fHorizontalResizeCursor); + else + SetViewCursor(B_CURSOR_SYSTEM_DEFAULT); + break; + + case B_EXITED_VIEW: + SetViewCursor(B_CURSOR_SYSTEM_DEFAULT); + break; + } + _inherited::MouseMoved(where, code, message); +} + +BColumnTitle * +BTitleView::InColumnResizeArea(BPoint where) const +{ + int32 count = fTitleList.CountItems(); + for (int32 index = 0; index < count; index++) { + BColumnTitle *title = fTitleList.ItemAt(index); + if (title->InColumnResizeArea(where)) + return title; + } + + return NULL; +} + +BColumnTitle * +BTitleView::FindColumnTitle(BPoint where) const +{ + int32 count = fTitleList.CountItems(); + for (int32 index = 0; index < count; index++) { + BColumnTitle *title = fTitleList.ItemAt(index); + if (title->Bounds().Contains(where)) + return title; + } + + return NULL; +} + +BColumnTitle * +BTitleView::FindColumnTitle(const BColumn *column) const +{ + int32 count = fTitleList.CountItems(); + for (int32 index = 0; index < count; index++) { + BColumnTitle *title = fTitleList.ItemAt(index); + if (title->Column() == column) + return title; + } + + return NULL; +} + +bool +BColumnTitle::InColumnResizeArea(BPoint where) const +{ + BRect edge(Bounds()); + edge.left = edge.right - kEdgeSize; + edge.right += kEdgeSize; + + return edge.Contains(where); +} + +BColumnTitle::BColumnTitle(BTitleView *view, BColumn *column) + : fColumn(column), + fParent(view) +{ +} + +BRect +BColumnTitle::Bounds() const +{ + BRect bounds(fColumn->Offset() - kTitleColumnLeftExtraMargin, 0, 0, kTitleViewHeight); + bounds.right = bounds.left + fColumn->Width() + kTitleColumnExtraMargin; + + return bounds; +} + +void +BColumnTitle::Draw(BView *view, bool pressed) +{ + BRect bounds(Bounds()); + BPoint loc(0, bounds.bottom - 4); + + if (pressed) + view->SetLowColor(kDarkTitleBackground); + else + view->SetLowColor(kTitleBackground); + + view->FillRect(bounds, B_SOLID_LOW); + + BString titleString(fColumn->Title()); + view->TruncateString(&titleString, B_TRUNCATE_END, + bounds.Width() - kTitleColumnExtraMargin); + float resultingWidth = view->StringWidth(titleString.String()); + + + switch (fColumn->Alignment()) { + case B_ALIGN_LEFT: + loc.x = bounds.left + 1 + kTitleColumnLeftExtraMargin; + break; + + case B_ALIGN_CENTER: + loc.x = bounds.left + (bounds.Width() / 2) - (resultingWidth / 2); + break; + + case B_ALIGN_RIGHT: + loc.x = bounds.right - resultingWidth - kTitleColumnRightExtraMargin; + break; + } + + view->MovePenTo(loc); + view->SetHighColor(0, 0, 0); + view->DrawString(titleString.String()); + + // show sort columns + bool secondary = (fColumn->AttrHash() == fParent->PoseView()->SecondarySort()); + if (secondary || (fColumn->AttrHash() == fParent->PoseView()->PrimarySort())) { + BPoint pt1(loc); + BPoint pt2(view->PenLocation()); + pt1.x--; + pt2.x--; + pt1.y++; + pt2.y++; + if (secondary) + view->StrokeLine(pt1, pt2, B_MIXED_COLORS); + else + view->StrokeLine(pt1, pt2); + } + + BRect rect(bounds); + + view->SetHighColor(kGray); + view->StrokeRect(rect); + + view->BeginLineArray(4); + // draw lighter gray and white inset lines + rect.InsetBy(1, 1); + view->AddLine(rect.LeftBottom(), rect.RightBottom(), + pressed ? kLightGray : kLightGray); + view->AddLine(rect.LeftTop(), rect.RightTop(), + pressed ? kDarkGray: kWhite); + + view->AddLine(rect.LeftTop(), rect.LeftBottom(), + pressed ? kDarkGray : kWhite); + view->AddLine(rect.RightTop(), rect.RightBottom(), + pressed ? kLightGray : kLightGray); + + view->EndLineArray(); +} + +ColumnTrackState::ColumnTrackState(BTitleView *view, BColumnTitle *title, + BPoint where) + : fTitleView(view), + fTitle(title), + fLastPos(where) +{ +} + +void +ColumnTrackState::MouseMoved(BPoint where, uint32 buttons) +{ + if (!ValueChanged(where)) + return; + + Moved(where, buttons); + fLastPos = where; +} + +ColumnResizeState::ColumnResizeState(BTitleView *view, BColumnTitle *title, + BPoint where) + : ColumnTrackState(view, title, where), + fLastLineDrawPos(-1), + fInitialTrackOffset((title->fColumn->Offset() + title->fColumn->Width()) - where.x) +{ + DrawLine(); +} + +bool +ColumnResizeState::ValueChanged(BPoint where) +{ + float newWidth = where.x + fInitialTrackOffset - fTitle->fColumn->Offset(); + if (newWidth < kMinColumnWidth) + newWidth = kMinColumnWidth; + + return newWidth != fTitle->fColumn->Width(); +} + +static void +_DrawLine(BPoseView *view, BPoint from, BPoint to) +{ + rgb_color highColor = view->HighColor(); + view->SetHighColor(kHighlightColor); + view->StrokeLine(from, to); + view->SetHighColor(highColor); +} + +static void +_UndrawLine(BPoseView *view, BPoint from, BPoint to) +{ + view->StrokeLine(from, to, B_SOLID_LOW); +} + +void +ColumnResizeState::Moved(BPoint where, uint32) +{ + float newWidth = where.x + fInitialTrackOffset - fTitle->fColumn->Offset(); + if (newWidth < kMinColumnWidth) + newWidth = kMinColumnWidth; + + BPoseView *poseView = fTitleView->PoseView(); + +// bool shrink = (newWidth < fTitle->fColumn->Width()); + + // resize the column + poseView->ResizeColumn(fTitle->fColumn, newWidth, &fLastLineDrawPos, + _DrawLine, _UndrawLine); + + BRect bounds(fTitleView->Bounds()); + bounds.left = fTitle->fColumn->Offset(); + + // force title redraw + fTitleView->Draw(bounds, true, false); +} + +void +ColumnResizeState::Done(BPoint) +{ + UndrawLine(); +} + +void +ColumnResizeState::Clicked(BPoint, uint32) +{ + UndrawLine(); +} + +void +ColumnResizeState::DrawLine() +{ + BPoseView *poseView = fTitleView->PoseView(); + ASSERT(!poseView->IsDesktopWindow()); + + BRect poseViewBounds(poseView->Bounds()); + // remember the line location + poseViewBounds.left = fTitle->Bounds().right; + fLastLineDrawPos = poseViewBounds.left; + + // draw the line in the new location + _DrawLine(poseView, poseViewBounds.LeftTop(), poseViewBounds.LeftBottom()); +} + +void +ColumnResizeState::UndrawLine() +{ + if (fLastLineDrawPos < 0) + return; + + BRect poseViewBounds(fTitleView->PoseView()->Bounds()); + poseViewBounds.left = fLastLineDrawPos; + + _UndrawLine(fTitleView->PoseView(), poseViewBounds.LeftTop(), + poseViewBounds.LeftBottom()); +} + + + +ColumnDragState::ColumnDragState(BTitleView *view, BColumnTitle *columnTitle, + BPoint where) + : ColumnTrackState(view, columnTitle, where), + fInitialMouseTrackOffset(where.x), + fTrackingRemovedColumn(false) +{ + ASSERT(columnTitle); + ASSERT(fTitle); + ASSERT(fTitle->Column()); + DrawPressNoOutline(); +} + +static void +_DrawOutline(BView *view, BRect where) +{ + where.InsetBy(1, 1); + rgb_color highColor = view->HighColor(); + view->SetHighColor(kHighlightColor); + view->StrokeRect(where); + view->SetHighColor(highColor); +} + +// ToDo: +// Autoscroll when dragging column left/right +// fix dragging back a column before the first column (now adds as last) +// make column swaps/adds not invalidate/redraw columns to the left +void +ColumnDragState::Moved(BPoint where, uint32) +{ + + // figure out where we are with the mouse + BRect titleBounds(fTitleView->Bounds()); + bool overTitleView = titleBounds.Contains(where); + BColumnTitle *overTitle = overTitleView + ? fTitleView->FindColumnTitle(where) : 0; + BRect titleBoundsWithMargin(titleBounds); + titleBoundsWithMargin.InsetBy(0, -kRemoveTitleMargin); + bool inMarginRect = overTitleView || titleBoundsWithMargin.Contains(where); + + bool drawOutline = false; + bool undrawOutline = false; + + if (fTrackingRemovedColumn) { + if (overTitleView) { + // tracked back with a removed title into the title bar, add it + // back + fTitleView->EndRectTracking(); + fColumnArchive.Seek(0, SEEK_SET); + BColumn *column = BColumn::InstantiateFromStream(&fColumnArchive); + ASSERT(column); + const BColumn *after = NULL; + if (overTitle) + after = overTitle->Column(); + fTitleView->PoseView()->AddColumn(column, after); + fTrackingRemovedColumn = false; + fTitle = fTitleView->FindColumnTitle(column); + fInitialMouseTrackOffset += fTitle->Bounds().left; + drawOutline = true; + } + } else { + if (!inMarginRect) { + // dragged a title out of the hysteresis margin around the + // title bar - remove it and start dragging it as a dotted outline + + BRect rect(fTitle->Bounds()); + rect.OffsetBy(where.x - fInitialMouseTrackOffset, where.y - 5); + fColumnArchive.Seek(0, SEEK_SET); + fTitle->Column()->ArchiveToStream(&fColumnArchive); + fInitialMouseTrackOffset -= fTitle->Bounds().left; + if (fTitleView->PoseView()->RemoveColumn(fTitle->Column(), false)) { + fTitle = 0; + fTitleView->BeginRectTracking(rect); + fTrackingRemovedColumn = true; + undrawOutline = true; + } + } else if (overTitle && overTitle != fTitle + // over a different column + && (overTitle->Bounds().left >= fTitle->Bounds().right + // over the one to the right + || where.x < overTitle->Bounds().left + fTitle->Bounds().Width())){ + // over the one to the left, far enough to not snap right back + + BColumn *column = fTitle->Column(); + fInitialMouseTrackOffset -= fTitle->Bounds().left; + // swap the columns + fTitleView->PoseView()->MoveColumnTo(column, overTitle->Column()); + // re-grab the title object looking it up by the column + fTitle = fTitleView->FindColumnTitle(column); + // recalc initialMouseTrackOffset + fInitialMouseTrackOffset += fTitle->Bounds().left; + drawOutline = true; + } else + drawOutline = true; + } + + if (drawOutline) + DrawOutline(where.x - fInitialMouseTrackOffset); + else if (undrawOutline) + UndrawOutline(); +} + +void +ColumnDragState::Done(BPoint) +{ + if (fTrackingRemovedColumn) + fTitleView->EndRectTracking(); + UndrawOutline(); +} + +void +ColumnDragState::Clicked(BPoint, uint32) +{ + BPoseView *poseView = fTitleView->PoseView(); + uint32 hash = fTitle->Column()->AttrHash(); + uint32 primarySort = poseView->PrimarySort(); + uint32 secondarySort = poseView->SecondarySort(); + bool shift = (modifiers() & B_SHIFT_KEY) != 0; + + // For now: + // if we hit the primary sort field again + // then if shift key was down, switch primary and secondary + if (hash == primarySort) { + if (shift && secondarySort) { + poseView->SetPrimarySort(secondarySort); + poseView->SetSecondarySort(primarySort); + } else + poseView->SetReverseSort(!poseView->ReverseSort()); + } else if (shift) { + // hit secondary sort column with shift key, disable + if (hash == secondarySort) + poseView->SetSecondarySort(0); + else + poseView->SetSecondarySort(hash); + } else { + poseView->SetPrimarySort(hash); + poseView->SetReverseSort(false); + } + + if (poseView->PrimarySort() == poseView->SecondarySort()) + poseView->SetSecondarySort(0); + + UndrawOutline(); + + poseView->SortPoses(); + poseView->Invalidate(); +} + +void +ColumnDragState::Pressing(BPoint, uint32) +{ +} + + +bool +ColumnDragState::ValueChanged(BPoint) +{ + return true; +} + +void +ColumnDragState::DrawPressNoOutline() +{ + fTitleView->Draw(fTitleView->Bounds(), true, false, fTitle); +} + +void +ColumnDragState::DrawOutline(float pos) +{ + BRect outline(fTitle->Bounds()); + outline.OffsetBy(pos, 0); + fTitleView->Draw(fTitleView->Bounds(), true, false, fTitle, _DrawOutline, outline); +} + +void +ColumnDragState::UndrawOutline() +{ + fTitleView->Draw(fTitleView->Bounds(), true, false); +} + + +OffscreenBitmap *BTitleView::offscreen = new OffscreenBitmap; diff --git a/src/kits/tracker/TitleView.h b/src/kits/tracker/TitleView.h new file mode 100644 index 0000000000..51987e5db4 --- /dev/null +++ b/src/kits/tracker/TitleView.h @@ -0,0 +1,208 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef _TITLE_VIEW_H +#define _TITLE_VIEW_H + +#include +#include +#include + +#include "ObjectList.h" + +namespace BPrivate { + +class BPoseView; +class BColumn; +class BColumnTitle; +class OffscreenBitmap; + +const int32 kTitleViewHeight = 16; +const int32 kEdgeSize = 6; +const int32 kTitleColumnLeftExtraMargin = 10; +const int32 kTitleColumnRightExtraMargin = 5; +const int32 kTitleColumnExtraMargin = kTitleColumnLeftExtraMargin + + kTitleColumnRightExtraMargin; +const int32 kMinColumnWidth = 20; +const int32 kRemoveTitleMargin = 10; +const int32 kColumnStart = 40; + +class BTitleView : public BView { +public: + BTitleView(BRect, BPoseView *); + virtual ~BTitleView(); + + virtual void MouseDown(BPoint); + virtual void Draw(BRect); + + void Draw(BRect, bool useOffscreen = false, + bool updateOnly = true, + const BColumnTitle *pressedColumn = 0, + void (*trackRectBlitter)(BView *, BRect) = 0, + BRect passThru = BRect(0, 0, 0, 0)); + + void AddTitle(BColumn *, const BColumn *after = 0); + void RemoveTitle(BColumn *); + void Reset(); + + BPoseView *PoseView() const; + +protected: + void MouseMoved(BPoint, uint32, const BMessage *); + +private: + BColumnTitle *FindColumnTitle(BPoint) const; + BColumnTitle *InColumnResizeArea(BPoint) const; + BColumnTitle *FindColumnTitle(const BColumn *) const; + + BPoseView *fPoseView; + BObjectList fTitleList; + BCursor fHorizontalResizeCursor; + + BColumnTitle *fPreviouslyClickedColumnTitle; + bigtime_t fPreviousLeftClickTime; + + static OffscreenBitmap *offscreen; + + typedef BView _inherited; + + friend class ColumnTrackState; + friend class ColumnDragState; +}; + +class BColumnTitle { +public: + BColumnTitle(BTitleView *, BColumn *); + virtual ~BColumnTitle() {} + + virtual void Draw(BView *, bool pressed = false); + + + BColumn *Column() const; + BRect Bounds() const; + + bool InColumnResizeArea(BPoint) const; + +private: + BColumn *fColumn; + BTitleView *fParent; + + friend class ColumnResizeState; +}; + +// Utility classes to handle dragging state +class ColumnTrackState { +public: + ColumnTrackState(BTitleView *, BColumnTitle *, BPoint where); + virtual ~ColumnTrackState() {} + + void MouseMoved(BPoint where, uint32 buttons); + + virtual void Moved(BPoint where, uint32 buttons) = 0; + virtual void Clicked(BPoint where, uint32 buttons) = 0; + virtual void Pressing(BPoint where, uint32 buttons) = 0; + // called if mouse held down too long for click but hasn't + // been moved a bit + virtual void Done(BPoint where) = 0; + +protected: + virtual bool ValueChanged(BPoint where) = 0; + + BTitleView *fTitleView; + BColumnTitle *fTitle; + BPoint fLastPos; +}; + +class ColumnResizeState : public ColumnTrackState { +public: + ColumnResizeState(BTitleView *, BColumnTitle *, BPoint); + +protected: + virtual void Moved(BPoint, uint32 buttons); + virtual void Done(BPoint); + virtual void Clicked(BPoint, uint32 buttons); + virtual void Pressing(BPoint, uint32) {} + virtual bool ValueChanged(BPoint); + + void DrawLine(); + void UndrawLine(); + +private: + float fLastLineDrawPos; + float fInitialTrackOffset; + + typedef ColumnTrackState _inherited; +}; + +class ColumnDragState : public ColumnTrackState { +public: + ColumnDragState(BTitleView *, BColumnTitle *, BPoint where); + +protected: + virtual void Moved(BPoint, uint32 buttons); + virtual void Done(BPoint); + virtual void Clicked(BPoint, uint32 buttons); + virtual void Pressing(BPoint, uint32 buttons); + virtual bool ValueChanged(BPoint); + + void DrawOutline(float); + void UndrawOutline(); + void DrawPressNoOutline(); + +private: + float fInitialMouseTrackOffset; + bool fTrackingRemovedColumn; + BMallocIO fColumnArchive; + + typedef ColumnTrackState _inherited; +}; + +inline BColumn * +BColumnTitle::Column() const +{ + return fColumn; +} + +inline BPoseView * +BTitleView::PoseView() const +{ + return fPoseView; +} + + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/Tracker.cpp b/src/kits/tracker/Tracker.cpp new file mode 100644 index 0000000000..df524481e8 --- /dev/null +++ b/src/kits/tracker/Tracker.cpp @@ -0,0 +1,1519 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Attributes.h" +#include "AutoLock.h" +#include "AutoMounter.h" +#include "AutoMounterSettings.h" +#include "BackgroundImage.h" +#include "Bitmaps.h" +#include "Commands.h" +#include "ContainerWindow.h" +#include "DeskWindow.h" +#include "FindPanel.h" +#include "FSClipboard.h" +#include "FSUtils.h" +#include "InfoWindow.h" +#include "MimeTypes.h" +#include "MimeTypeList.h" +#include "NodePreloader.h" +#include "OpenWithWindow.h" +#include "PoseView.h" +#include "QueryContainerWindow.h" +#include "StatusWindow.h" +#include "Tracker.h" +#include "TrackerSettings.h" +#include "TrashWatcher.h" +#include "FunctionObject.h" +#include "TrackerSettings.h" +#include "TrackerSettingsWindow.h" +#include "TaskLoop.h" +#include "Thread.h" +#include "Utilities.h" +#include "VolumeWindow.h" + +// PPC binary compatibility. +#include "AboutBox.cpp" + +// prototypes for some private kernel calls that will some day be public +#if B_BEOS_VERSION_DANO +#define _IMPEXP_ROOT +#endif +extern "C" _IMPEXP_ROOT int _kset_fd_limit_(int num); +extern "C" _IMPEXP_ROOT int _kset_mon_limit_(int num); +#if B_BEOS_VERSION_DANO +#undef _IMPEXP_ROOT +#endif + // from priv_syscalls.h + +const int32 DEFAULT_MON_NUM = 4096; + // copied from fsil.c + +const int8 kOpenWindowNoFlags = 0; +const int8 kOpenWindowMinimized = 1; +const int8 kOpenWindowHasState = 2; + +const uint32 PSV_MAKE_PRINTER_ACTIVE_QUIETLY = 'pmaq'; + // from pr_server.h + + +namespace BPrivate { + +NodePreloader *gPreloader = NULL; + +void +InitIconPreloader() +{ + static int32 lock = 0; + + if (atomic_add(&lock, 1) != 0) { + // Just wait for the icon cache to be instantiated + int32 tries = 20; + while (IconCache::sIconCache == NULL && tries-- > 0) + snooze(10000); + return; + } + + if (IconCache::sIconCache != NULL) + return; + + // only start the node preloader if its Tracker or the Deskbar itself - don't + // start it for file panels + + bool preload = dynamic_cast(be_app) != NULL; + if (!preload) { + // check for deskbar + app_info info; + if (be_app->GetAppInfo(&info) == B_OK + && !strcmp(info.signature, kDeskbarSignature)) + preload = true; + } + if (preload) + gPreloader = NodePreloader::InstallNodePreloader("NodePreloader", be_app); + + IconCache::sIconCache = new IconCache(); + + atomic_add(&lock, -1); +} + +} // namespace BPrivate + + +uint32 +GetVolumeFlags(Model *model) +{ + fs_info info; + if (model->IsVolume()) { + // search for the correct volume + int32 cookie = 0; + dev_t device; + while ((device = next_dev(&cookie)) >= B_OK) { + if (fs_stat_dev(device,&info)) + continue; + + if (!strcmp(info.volume_name,model->Name())) + return info.flags; + } + return B_FS_HAS_ATTR; + } + if (!fs_stat_dev(model->NodeRef()->device,&info)) + return info.flags; + + return B_FS_HAS_ATTR; +} + + +static void +HideVarDir() +{ + BPath path; + status_t err = find_directory(B_COMMON_VAR_DIRECTORY, &path); + + if (err != B_OK){ + PRINT(("var err = %s\n", strerror(err))); + return; + } + + BDirectory varDirectory(path.Path()); + if (varDirectory.InitCheck() == B_OK) { + PoseInfo info; + // make var dir invisible + info.fInvisible = true; + info.fInitedDirectory = -1; + + if (varDirectory.WriteAttr(kAttrPoseInfo, B_RAW_TYPE, 0, &info, sizeof(info)) + == sizeof(info)) + varDirectory.RemoveAttr(kAttrPoseInfoForeign); + } +} + + +// #pragma mark - + + +TTracker::TTracker() + : BApplication(kTrackerSignature), + fSettingsWindow(NULL) +{ + // set the cwd to /boot/home, anything that's launched + // from Tracker will automatically inherit this + BPath homePath; + + if (find_directory(B_USER_DIRECTORY, &homePath) == B_OK) + chdir(homePath.Path()); + + _kset_fd_limit_(512); + // ask for a bunch more file descriptors so that nested copying + // works well + + fNodeMonitorCount = DEFAULT_MON_NUM; + +#ifdef CHECK_OPEN_MODEL_LEAKS + InitOpenModelDumping(); +#endif + + InitIconPreloader(); + +#ifdef LEAK_CHECKING + SetNewLeakChecking(true); + SetMallocLeakChecking(true); +#endif + + //This is how often it should update the free space bar on the volume icons + SetPulseRate(1000000); +} + + +TTracker::~TTracker() +{ +} + + +bool +TTracker::QuitRequested() +{ + // don't allow user quitting + if (CurrentMessage() && CurrentMessage()->FindBool("shortcut")) + return false; + + gStatusWindow->AttemptToQuit(); + // try quitting the copy/move/empty trash threads + + BVolume bootVolume; + DEBUG_ONLY(status_t err =) BVolumeRoster().GetBootVolume(&bootVolume); + ASSERT(err == B_OK); + BMessage message; + AutoLock lock(&fWindowList); + // save open windows in a message inside an attribute of the desktop + int32 count = fWindowList.CountItems(); + for (int32 i = 0; i < count; i++) { + BContainerWindow *window = dynamic_cast + (fWindowList.ItemAt(i)); + + if (window && window->TargetModel() && !window->PoseView()->IsDesktopWindow()) { + if (window->TargetModel()->IsRoot()) + message.AddBool("open_disks_window", true); + else { + BEntry entry; + BPath path; + const entry_ref *ref = window->TargetModel()->EntryRef(); + if (entry.SetTo(ref) == B_OK && entry.GetPath(&path) == B_OK) { + int8 flags = window->IsMinimized() ? kOpenWindowMinimized : kOpenWindowNoFlags; + uint32 deviceFlags = GetVolumeFlags(window->TargetModel()); + + // save state for every window which is + // a) already open on another workspace + // b) on a volume not capable of writing attributes + if (window != FindContainerWindow(ref) + || (deviceFlags & (B_FS_HAS_ATTR | B_FS_IS_READONLY)) != B_FS_HAS_ATTR) { + BMessage stateMessage; + window->SaveState(stateMessage); + window->SetSaveStateEnabled(false); + // This is to prevent its state to be saved to the node when closed. + message.AddMessage("window state", &stateMessage); + flags |= kOpenWindowHasState; + } + const char *target; + bool pathAlreadyExists = false; + for (int32 index = 0;message.FindString("paths", index, &target) == B_OK;index++) { + if (!strcmp(target,path.Path())) { + pathAlreadyExists = true; + break; + } + } + if (!pathAlreadyExists) + message.AddString("paths", path.Path()); + message.AddInt8(path.Path(), flags); + } + } + } + } + lock.Unlock(); + + // write windows to open on disk + BDirectory deskDir; + if (!BootedInSafeMode() && FSGetDeskDir(&deskDir, bootVolume.Device()) == B_OK) { + // if message is empty, delete the corresponding attribute + if (message.CountNames(B_ANY_TYPE)) { + size_t size = (size_t)message.FlattenedSize(); + char *buffer = new char[size]; + message.Flatten(buffer, (ssize_t)size); + deskDir.WriteAttr(kAttrOpenWindows, B_MESSAGE_TYPE, 0, buffer, size); + delete [] buffer; + } else + deskDir.RemoveAttr(kAttrOpenWindows); + } + + for (int32 count = 0; count == 50; count++) { + // wait 5 seconds for the copiing/moving to quit + if (gStatusWindow->AttemptToQuit()) + break; + + snooze(100000); + } + + return _inherited::QuitRequested(); +} + + +void +TTracker::Quit() +{ + TrackerSettings().SaveSettings(false); + + fAutoMounter->Lock(); + fAutoMounter->QuitRequested(); // automounter does some stuff in QuitRequested + fAutoMounter->Quit(); // but we really don't care if it is cooperating or not + + fClipboardRefsWatcher->Lock(); + fClipboardRefsWatcher->Quit(); + + fTrashWatcher->Lock(); + fTrashWatcher->Quit(); + + WellKnowEntryList::Quit(); + + delete gPreloader; + delete fTaskLoop; + delete IconCache::sIconCache; + + _inherited::Quit(); +} + + +void +TTracker::MessageReceived(BMessage *message) +{ + if (HandleScriptingMessage(message)) + return; + + switch (message->what) { + case kGetInfo: + OpenInfoWindows(message); + break; + + case kMoveToTrash: + MoveRefsToTrash(message); + break; + + case kCloseWindowAndChildren: + { + const node_ref *itemNode; + int32 bytes; + message->FindData("node_ref", B_RAW_TYPE, + (const void **)&itemNode, &bytes); + CloseWindowAndChildren(itemNode); + break; + } + + case kCloseAllWindows: + CloseAllWindows(); + break; + + case kFindButton: + (new FindWindow())->Show(); + break; + + case kEditQuery: + EditQueries(message); + break; + + case kUnmountVolume: + // When the user attempts to unmount a volume from the mount + // context menu, this is where the message gets received. Save + // pose locations and forward this to the automounter + SaveAllPoseLocations(); + fAutoMounter->PostMessage(message); + break; + + case kRunAutomounterSettings: + AutomountSettingsDialog::RunAutomountSettings(fAutoMounter); + break; + + case kShowSplash: + { + // The AboutWindow was moved out of the Tracker in preparation + // for when we open source it. The AboutBox contains important + // credit and license issues that shouldn't be modified, and + // therefore shouldn't be open sourced. However, there is a public + // API for 3rd party apps to tell the Tracker to open the AboutBox. + run_be_about(); + break; + } + + case kAddPrinter: + // show the addprinter window + run_add_printer_panel(); + break; + + case kMakeActivePrinter: + // get the current selection + SetDefaultPrinter(message); + break; + +#ifdef MOUNT_MENU_IN_DESKBAR + + case 'gmtv': + { + // Someone (probably the deskbar) has requested a list of + // mountable volumes. + BMessage reply; + AutoMounterLoop()->EachMountableItemAndFloppy(&AddMountableItemToMessage, + &reply); + message->SendReply(&reply); + break; + } + +#endif + + case kMountVolume: + case kMountAllNow: + AutoMounterLoop()->PostMessage(message); + break; + + + case kRestoreBackgroundImage: + { + BDeskWindow *desktop = GetDeskWindow(); + AutoLock lock(desktop); + desktop->UpdateDesktopBackgroundImages(); + } + break; + + case kShowSettingsWindow: + ShowSettingsWindow(); + break; + + case kFavoriteCountChangedExternally: + SendNotices(kFavoriteCountChangedExternally, message); + break; + + case kStartWatchClipboardRefs: + { + BMessenger messenger; + message->FindMessenger("target", &messenger); + if (messenger.IsValid()) + fClipboardRefsWatcher->AddToNotifyList(messenger); + break; + } + + case kStopWatchClipboardRefs: + { + BMessenger messenger; + message->FindMessenger("target", &messenger); + if (messenger.IsValid()) + fClipboardRefsWatcher->RemoveFromNotifyList(messenger); + break; + } + + case kFSClipboardChanges: + { + fClipboardRefsWatcher->UpdatePoseViews(message); + break; + } + + default: + _inherited::MessageReceived(message); + break; + } +} + + +void +TTracker::Pulse() +{ + if (!TrackerSettings().ShowVolumeSpaceBar()) + return; + + // update the volume icon's free space bars + BVolumeRoster roster; + + BVolume volume; + while (roster.GetNextVolume(&volume) == B_OK) { + BDirectory dir; + volume.GetRootDirectory(&dir); + node_ref nodeRef; + dir.GetNodeRef(&nodeRef); + + BMessage notificationMessage; + notificationMessage.AddInt32("device", *(int32 *)&nodeRef.device); + + SendNotices(kUpdateVolumeSpaceBar, ¬ificationMessage); + } +} + + +void +TTracker::SetDefaultPrinter(const BMessage *message) +{ + // get the first item selected + int32 count = 0; + uint32 type = 0; + message->GetInfo("refs", &type, &count); + + if (count <= 0) + return; + + // will make the first item the default printer, disregards any other files + entry_ref ref; + ASSERT(message->FindRef("refs", 0, &ref) == B_OK); + if (message->FindRef("refs", 0, &ref) != B_OK) + return; + +#if B_BEOS_VERSION_DANO + set_default_printer(ref.name); +#else + // create a message for the print server + BMessenger messenger("application/x-vnd.Be-PSRV", -1); + if (!messenger.IsValid()) + return; + + // send the selection to the print server + BMessage makeActiveMessage(PSV_MAKE_PRINTER_ACTIVE_QUIETLY); + makeActiveMessage.AddString("printer", ref.name); + + BMessage reply; + messenger.SendMessage(&makeActiveMessage, &reply); +#endif +} + + +void +TTracker::MoveRefsToTrash(const BMessage *message) +{ + int32 count; + uint32 type; + message->GetInfo("refs", &type, &count); + + if (count <= 0) + return; + + BObjectList *srcList = new BObjectList(count, true); + + for (int32 index = 0; index < count; index++) { + + entry_ref ref; + ASSERT(message->FindRef("refs", index, &ref) == B_OK); + if (message->FindRef("refs", index, &ref) != B_OK) + continue; + + AutoLock lock(&fWindowList); + BContainerWindow *window = FindParentContainerWindow(&ref); + if (window) + // if we have a window open for this entry, ask the pose to + // delete it, this will select the next entry + window->PoseView()->MoveEntryToTrash(&ref); + else + // add all others to a list that gets deleted separately + srcList->AddItem(new entry_ref(ref)); + } + + if (srcList->CountItems()) + // async move to trash + FSMoveToTrash(srcList); +} + + +template +class EntryAndNodeDoSoonWithMessageFunctor : public FunctionObjectWithResult { +public: + EntryAndNodeDoSoonWithMessageFunctor(FT func, T *target, const entry_ref *child, + const node_ref *parent, const BMessage *message) + : fFunc(func), + fTarget(target), + fNode(*parent), + fEntry(*child) + { + fSendMessage = (message != NULL); + if (message) + fMessage = *message; + } + + virtual ~EntryAndNodeDoSoonWithMessageFunctor() {} + virtual void operator()() + { result = (fTarget->*fFunc)(&fEntry, &fNode, fSendMessage ? &fMessage : NULL); } + +protected: + FT fFunc; + T *fTarget; + node_ref fNode; + entry_ref fEntry; + BMessage fMessage; + bool fSendMessage; +}; + + +bool +TTracker::LaunchAndCloseParentIfOK(const entry_ref *launchThis, + const node_ref *closeThis, const BMessage *messageToBundle) +{ + BMessage refsReceived(B_REFS_RECEIVED); + if (messageToBundle) { + refsReceived = *messageToBundle; + refsReceived.what = B_REFS_RECEIVED; + } + refsReceived.AddRef("refs", launchThis); + // synchronous launch, we are already in our own thread + if (TrackerLaunch(&refsReceived, false) == B_OK) { + // if launched fine, close parent window in a bit + fTaskLoop->RunLater(NewMemberFunctionObject(&TTracker::CloseParent, this, *closeThis), + 1000000); + } + return false; +} + + +status_t +TTracker::OpenRef(const entry_ref *ref, const node_ref *nodeToClose, + const node_ref *nodeToSelect, OpenSelector selector, + const BMessage *messageToBundle) +{ + Model *model = NULL; + BEntry entry(ref, true); + status_t result = entry.InitCheck(); + + bool brokenLinkWithSpecificHandler = false; + BString brokenLinkPreferredApp; + + if (result != B_OK) { + model = new Model(ref, false); + if (model->IsSymLink() && !model->LinkTo()) { + model->GetPreferredAppForBrokenSymLink(brokenLinkPreferredApp); + if (brokenLinkPreferredApp.Length() && brokenLinkPreferredApp != kTrackerSignature) + brokenLinkWithSpecificHandler = true; + } + + if (!brokenLinkWithSpecificHandler) { + delete model; + (new BAlert("", "There was an error resolving the link.", + "Cancel", 0, 0, + B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); + return result; + } + } else + model = new Model(&entry); + + result = model->InitCheck(); + if (result != B_OK) { + delete model; + return result; + } + + bool openAsContainer = model->IsContainer(); + + if (openAsContainer && selector != kOpenWith) { + // if folder or query has a preferred handler and it's not the + // Tracker, open it by sending refs to the handling app + + // if we are responding to the final open of OpenWith, just + // skip this and proceed to opening the container with Tracker + model->OpenNode(); + BNodeInfo nodeInfo(model->Node()); + char preferredApp[B_MIME_TYPE_LENGTH]; + if (nodeInfo.GetPreferredApp(preferredApp) == B_OK + && strcasecmp(preferredApp, kTrackerSignature) != 0) + openAsContainer = false; + model->CloseNode(); + } + + if (openAsContainer || selector == kRunOpenWithWindow) { + // special case opening plain folders, queries or using open with + OpenContainerWindow(model, 0, selector); // window adopts model + if (nodeToClose) + CloseParentWaitingForChildSoon(ref, nodeToClose); + } else if (model->IsQueryTemplate()) { + // query template - open new find window + (new FindWindow(model->EntryRef()))->Show(); + if (nodeToClose) + CloseParentWaitingForChildSoon(ref, nodeToClose); + } else { + delete model; + // run Launch in a separate thread + // and close parent if successfull + if (nodeToClose) + Thread::Launch(new EntryAndNodeDoSoonWithMessageFunctor(&TTracker::LaunchAndCloseParentIfOK, this, + ref, nodeToClose, messageToBundle)); + else { + BMessage refsReceived(B_REFS_RECEIVED); + if (messageToBundle) { + refsReceived = *messageToBundle; + refsReceived.what = B_REFS_RECEIVED; + } + refsReceived.AddRef("refs", ref); + if (brokenLinkWithSpecificHandler) + // This cruft is to support a hacky workaround for double-clicking + // broken refs for cifs; should get fixed in R5 + LaunchBrokenLink(brokenLinkPreferredApp.String(), &refsReceived); + else + TrackerLaunch(&refsReceived, true); + } + } + if (nodeToSelect) + SelectChildInParentSoon(ref, nodeToSelect); + + return B_OK; +} + + +void +TTracker::RefsReceived(BMessage *message) +{ + OpenSelector selector = kOpen; + if (message->HasInt32("launchUsingSelector")) + selector = kRunOpenWithWindow; + + entry_ref handlingApp; + if (message->FindRef("handler", &handlingApp) == B_OK) + selector = kOpenWith; + + int32 count; + uint32 type; + message->GetInfo("refs", &type, &count); + + switch (selector) { + case kRunOpenWithWindow: + OpenContainerWindow(0, message, selector); + // window adopts model + break; + + case kOpenWith: + { + // Open With resulted in passing refs and a handler, open the files + // with the handling app + message->RemoveName("handler"); + + // have to find out if handling app is the Tracker + // if it is, just pass it to the active Tracker, no matter which Tracker + // was chosen to handle the refs + char signature[B_MIME_TYPE_LENGTH]; + signature[0] = '\0'; + { + BFile handlingNode(&handlingApp, O_RDONLY); + BAppFileInfo appInfo(&handlingNode); + appInfo.GetSignature(signature); + } + + if (strcasecmp(signature, kTrackerSignature) != 0) { + // handling app not Tracker, pass entries to the apps RefsReceived + TrackerLaunch(&handlingApp, message, true); + break; + } + // fall thru, opening refs by the Tracker, as if they were double clicked + } + + case kOpen: + { + // copy over "Poses" messenger so that refs received recipients know + // where the open came from + BMessage *bundleThis = NULL; + BMessenger messenger; + if (message->FindMessenger("TrackerViewToken", &messenger) == B_OK) { + bundleThis = new BMessage(); + bundleThis->AddMessenger("TrackerViewToken", messenger); + } + + for (int32 index = 0; index < count; index++) { + entry_ref ref; + message->FindRef("refs", index, &ref); + + const node_ref *nodeToClose = NULL; + const node_ref *nodeToSelect = NULL; + ssize_t numBytes; + + message->FindData("nodeRefsToClose", B_RAW_TYPE, index, + (const void **)&nodeToClose, &numBytes); + message->FindData("nodeRefToSelect", B_RAW_TYPE, index, + (const void **)&nodeToSelect, &numBytes); + + OpenRef(&ref, nodeToClose, nodeToSelect, selector, bundleThis); + } + + delete bundleThis; + break; + } + } +} + + +void +TTracker::ArgvReceived(int32 argc, char **argv) +{ + BMessage *message = CurrentMessage(); + const char *currentWorkingDirectoryPath = NULL; + entry_ref ref; + + if (message->FindString("cwd", ¤tWorkingDirectoryPath) == B_OK) { + BDirectory workingDirectory(currentWorkingDirectoryPath); + for (int32 index = 1; index < argc; index++) { + BEntry entry; + if (entry.SetTo(&workingDirectory, argv[index]) == B_OK + && entry.GetRef(&ref) == B_OK) + OpenRef(&ref); + else if (get_ref_for_path(argv[index], &ref) == B_OK) + OpenRef(&ref); + } + } +} + + +void +TTracker::OpenContainerWindow(Model *model, BMessage *originalRefsList, + OpenSelector openSelector, uint32 openFlags, bool checkAlreadyOpen, + const BMessage *stateMessage) +{ + AutoLock lock(&fWindowList); + BContainerWindow *window = NULL; + if (checkAlreadyOpen && openSelector != kRunOpenWithWindow) + // find out if window already open + window = FindContainerWindow(model->NodeRef()); + + bool someWindowActivated = false; + + uint32 workspace = (uint32)(1 << current_workspace()); + int32 windowCount = 0; + + while (window) { + // At least one window open, just pull to front + // make sure we don't jerk workspaces around + uint32 windowWorkspaces = window->Workspaces(); + if (windowWorkspaces & workspace) { + window->Activate(); + someWindowActivated = true; + } + window = FindContainerWindow(model->NodeRef(), ++windowCount); + } + + if (someWindowActivated) { + delete model; + return; + } // If no window was actiated, (none in the current workspace + // we open a new one. + + if (openSelector == kRunOpenWithWindow) { + BMessage *refList = NULL; + if (!originalRefsList) { + // when passing just a single model, stuff it's entry in a single + // element list anyway + ASSERT(model); + refList = new BMessage; + refList->AddRef("refs", model->EntryRef()); + delete model; + model = NULL; + } else + // clone the message, window adopts it for it's own use + refList = new BMessage(*originalRefsList); + window = new OpenWithContainerWindow(refList, &fWindowList); + } else if (model->IsRoot()) { + // window will adopt the model + window = new BVolumeWindow(&fWindowList, openFlags); + } else if (model->IsQuery()) { + // window will adopt the model + window = new BQueryContainerWindow(&fWindowList, openFlags); + } else + // window will adopt the model + window = new BContainerWindow(&fWindowList, openFlags); + + if (model) + window->CreatePoseView(model); + + BMessage restoreStateMessage(kRestoreState); + + if (stateMessage) + restoreStateMessage.AddMessage("state", stateMessage); + + window->PostMessage(&restoreStateMessage); +} + + +void +TTracker::EditQueries(const BMessage *message) +{ + bool editOnlyIfTemplate; + if (message->FindBool("editQueryOnPose", &editOnlyIfTemplate) != B_OK) + editOnlyIfTemplate = false; + + type_code type; + int32 count; + message->GetInfo("refs", &type, &count); + for (int32 index = 0; index < count; index++) { + entry_ref ref; + message->FindRef("refs", index, &ref); + BEntry entry(&ref, true); + if (entry.InitCheck() == B_OK && entry.Exists()) + (new FindWindow(&ref, editOnlyIfTemplate))->Show(); + } +} + + +void +TTracker::OpenInfoWindows(BMessage *message) +{ + type_code type; + int32 count; + message->GetInfo("refs", &type, &count); + + for (int32 index = 0; index < count; index++) { + entry_ref ref; + message->FindRef("refs", index, &ref); + BEntry entry; + if (entry.SetTo(&ref) == B_OK) { + Model *model = new Model(&entry); + if (model->InitCheck() != B_OK) { + delete model; + continue; + } + + AutoLock lock(&fWindowList); + BInfoWindow *wind = FindInfoWindow(model->NodeRef()); + + if (wind) { + wind->Activate(); + delete model; + } else { + wind = new BInfoWindow(model, index, &fWindowList); + wind->PostMessage(kRestoreState); + } + } + } +} + + +BDeskWindow * +TTracker::GetDeskWindow() const +{ + int32 count = fWindowList.CountItems(); + for (int32 index = 0; index < count; index++) { + BDeskWindow *window = dynamic_cast + (fWindowList.ItemAt(index)); + + if (window) + return window; + } + TRESPASS(); + return NULL; +} + + +BContainerWindow * +TTracker::FindContainerWindow(const node_ref *node, int32 number) const +{ + ASSERT(fWindowList.IsLocked()); + + int32 count = fWindowList.CountItems(); + + int32 windowsFound = 0; + + for (int32 index = 0; index < count; index++) { + BContainerWindow *window = dynamic_cast + (fWindowList.ItemAt(index)); + + if (window && window->IsShowing(node) && number == windowsFound++) + return window; + } + return NULL; +} + + +BContainerWindow * +TTracker::FindContainerWindow(const entry_ref *entry, int32 number) const +{ + ASSERT(fWindowList.IsLocked()); + + int32 count = fWindowList.CountItems(); + + int32 windowsFound = 0; + + for (int32 index = 0; index < count; index++) { + BContainerWindow *window = dynamic_cast + (fWindowList.ItemAt(index)); + + if (window && window->IsShowing(entry) && number == windowsFound++) + return window; + } + return NULL; +} + + +bool +TTracker::EntryHasWindowOpen(const entry_ref *entry) +{ + AutoLock lock(&fWindowList); + return FindContainerWindow(entry) != NULL; +} + + +BContainerWindow * +TTracker::FindParentContainerWindow(const entry_ref *ref) const +{ + BEntry entry(ref); + BEntry parent; + + if (entry.GetParent(&parent) != B_OK) + return NULL; + + entry_ref parentRef; + parent.GetRef(&parentRef); + + ASSERT(fWindowList.IsLocked()); + + int32 count = fWindowList.CountItems(); + for (int32 index = 0; index < count; index++) { + BContainerWindow *window = dynamic_cast + (fWindowList.ItemAt(index)); + if (window && window->IsShowing(&parentRef)) + return window; + } + return NULL; +} + + +BInfoWindow * +TTracker::FindInfoWindow(const node_ref* node) const +{ + ASSERT(fWindowList.IsLocked()); + + int32 count = fWindowList.CountItems(); + for (int32 index = 0; index < count; index++) { + BInfoWindow *window = dynamic_cast + (fWindowList.ItemAt(index)); + if (window && window->IsShowing(node)) + return window; + } + return NULL; +} + + +bool +TTracker::QueryActiveForDevice(dev_t device) +{ + AutoLock lock(&fWindowList); + int32 count = fWindowList.CountItems(); + for (int32 index = 0; index < count; index++) { + BQueryContainerWindow *window = dynamic_cast + (fWindowList.ItemAt(index)); + if (window) { + AutoLock lock(window); + if (window->ActiveOnDevice(device)) + return true; + } + } + return false; +} + + +void +TTracker::CloseActiveQueryWindows(dev_t device) +{ + // used when trying to unmount a volume - an active query would prevent that from + // happening + bool closed = false; + AutoLock lock(fWindowList); + for (int32 index = fWindowList.CountItems(); index >= 0; index--) { + BQueryContainerWindow *window = dynamic_cast + (fWindowList.ItemAt(index)); + if (window) { + AutoLock lock(window); + if (window->ActiveOnDevice(device)) { + window->PostMessage(B_QUIT_REQUESTED); + closed = true; + } + } + } + lock.Unlock(); + if (closed) + for (int32 timeout = 30; timeout; timeout--) { + // wait a bit for windows to fully close + if (!QueryActiveForDevice(device)) + return; + snooze(100000); + } +} + + +void +TTracker::SaveAllPoseLocations() +{ + int32 numWindows = fWindowList.CountItems(); + for (int32 windowIndex = 0; windowIndex < numWindows; windowIndex++) { + BContainerWindow *window = dynamic_cast + (fWindowList.ItemAt(windowIndex)); + + if (window) { + AutoLock lock(window); + BDeskWindow *deskWindow = dynamic_cast(window); + + if (deskWindow) + deskWindow->SaveDesktopPoseLocations(); + else + window->PoseView()->SavePoseLocations(); + } + } +} + + +void +TTracker::CloseWindowAndChildren(const node_ref *node) +{ + BDirectory dir(node); + if (dir.InitCheck() != B_OK) + return; + + AutoLock lock(&fWindowList); + BObjectList closeList; + + // make a list of all windows to be closed + // count from end to beginning so we can remove items safely + for (int32 index = fWindowList.CountItems() - 1; index >= 0; index--) { + BContainerWindow *window = dynamic_cast + (fWindowList.ItemAt(index)); + if (window && window->TargetModel()) { + BEntry wind_entry; + wind_entry.SetTo(window->TargetModel()->EntryRef()); + + if ((*window->TargetModel()->NodeRef() == *node) + || dir.Contains(&wind_entry)) { + + // ToDo: + // get rid of the Remove here, BContainerWindow::Quit does it + fWindowList.RemoveItemAt(index); + closeList.AddItem(window); + } + } + } + + // now really close the windows + int32 numItems = closeList.CountItems(); + for (int32 index = 0; index < numItems; index++) { + BContainerWindow *window = closeList.ItemAt(index); + window->PostMessage(B_QUIT_REQUESTED); + } +} + + +void +TTracker::CloseAllWindows() +{ + // this is a response to the DeskBar sending us a B_QUIT, when it really + // means to say close all your windows. It might be better to have it + // send a kCloseAllWindows message and have windowless apps stay running, + // which is what we will do for the Tracker + AutoLock lock(&fWindowList); + + int32 count = CountWindows(); + for (int32 index = 0; index < count; index++) { + BWindow *window = WindowAt(index); + // avoid the desktop + if (!dynamic_cast(window) + && !dynamic_cast(window)) + window->PostMessage(B_QUIT_REQUESTED); + } + // count from end to beginning so we can remove items safely + for (int32 index = fWindowList.CountItems() - 1; index >= 0; index--) { + BWindow *window = fWindowList.ItemAt(index); + if (!dynamic_cast(window) + && !dynamic_cast(window)) + // ToDo: + // get rid of the Remove here, BContainerWindow::Quit does it + fWindowList.RemoveItemAt(index); + } +} + + +void +TTracker::ReadyToRun() +{ + gStatusWindow = new BStatusWindow(); + InitMimeTypes(); + InstallDefaultTemplates(); + InstallIndices(); + + HideVarDir(); + + fTrashWatcher = new BTrashWatcher(); + fTrashWatcher->Run(); + + fClipboardRefsWatcher = new BClipboardRefsWatcher(); + fClipboardRefsWatcher->Run(); + + fAutoMounter = new AutoMounter(); + fAutoMounter->Run(); + + fTaskLoop = new StandAloneTaskLoop(true); + + bool openDisksWindow = false; + + // open desktop window + BContainerWindow *deskWindow = NULL; + BVolume bootVol; + BVolumeRoster().GetBootVolume(&bootVol); + BDirectory deskDir; + if (FSGetDeskDir(&deskDir, bootVol.Device()) == B_OK) { + // create desktop + BEntry entry; + deskDir.GetEntry(&entry); + Model *model = new Model(&entry); + if (model->InitCheck() == B_OK) { + AutoLock lock(&fWindowList); + deskWindow = new BDeskWindow(&fWindowList); + AutoLock windowLock(deskWindow); + deskWindow->CreatePoseView(model); + deskWindow->Init(); + } else + delete model; + + // open previously open windows + attr_info attrInfo; + if (!BootedInSafeMode() + && deskDir.GetAttrInfo(kAttrOpenWindows, &attrInfo) == B_OK) { + char *buffer = (char *)malloc((size_t)attrInfo.size); + BMessage message; + if (deskDir.ReadAttr(kAttrOpenWindows, B_MESSAGE_TYPE, 0, buffer, (size_t)attrInfo.size) + == attrInfo.size + && message.Unflatten(buffer) == B_OK) { + + node_ref nodeRef; + deskDir.GetNodeRef(&nodeRef); + + int32 stateMessageCounter = 0; + const char *path; + for (int32 outer = 0;message.FindString("paths", outer, &path) == B_OK;outer++) { + int8 flags = 0; + for (int32 inner = 0;message.FindInt8(path, inner, &flags) == B_OK;inner++) { + BEntry entry(path, true); + if (entry.InitCheck() == B_OK) { + Model *model = new Model(&entry); + if (model->InitCheck() == B_OK && model->IsContainer()) { + BMessage state; + bool restoreStateFromMessage = false; + if ((flags & kOpenWindowHasState) != 0 + && message.FindMessage("window state", stateMessageCounter++, &state) == B_OK) + restoreStateFromMessage = true; + + if (restoreStateFromMessage) + OpenContainerWindow(model, 0, kOpen, + kRestoreWorkspace | (flags & kOpenWindowMinimized ? kIsHidden : 0U), + false, &state); + else + OpenContainerWindow(model, 0, kOpen, + kRestoreWorkspace | (flags & kOpenWindowMinimized ? kIsHidden : 0U)); + } else + delete model; + } + } + } + + if (message.HasBool("open_disks_window")) + openDisksWindow = true; + } + free(buffer); + } + } + + // create model for root of everything + if (deskWindow) { + BEntry entry("/"); + Model model(&entry); + if (model.InitCheck() == B_OK) { + + if (TrackerSettings().ShowDisksIcon()) { + // add the root icon to desktop window + BMessage message; + message.what = B_NODE_MONITOR; + message.AddInt32("opcode", B_ENTRY_CREATED); + message.AddInt32("device", model.NodeRef()->device); + message.AddInt64("node", model.NodeRef()->node); + message.AddInt64("directory", model.EntryRef()->directory); + message.AddString("name", model.EntryRef()->name); + deskWindow->PostMessage(&message, deskWindow->PoseView()); + } + + if (openDisksWindow) + OpenContainerWindow(new Model(model), 0, kOpen, kRestoreWorkspace); + } + } + + // kick off building the mime type list for find panels, etc. + fMimeTypeList = new MimeTypeList(); + + if (!BootedInSafeMode()) + // kick of transient query killer + DeleteTransientQueriesTask::StartUpTransientQueryCleaner(); +} + +MimeTypeList * +TTracker::MimeTypes() const +{ + return fMimeTypeList; +} + +void +TTracker::SelectChildInParentSoon(const entry_ref *parent, + const node_ref *child) +{ + fTaskLoop->RunLater(NewMemberFunctionObjectWithResult + (&TTracker::SelectChildInParent, this, parent, child), + 100000, 200000, 5000000); +} + +void +TTracker::CloseParentWaitingForChildSoon(const entry_ref *child, + const node_ref *parent) +{ + fTaskLoop->RunLater(NewMemberFunctionObjectWithResult + (&TTracker::CloseParentWaitingForChild, this, child, parent), + 200000, 100000, 5000000); +} + +void +TTracker::SelectPoseAtLocationSoon(node_ref parent, BPoint pointInPose) +{ + fTaskLoop->RunLater(NewMemberFunctionObject + (&TTracker::SelectPoseAtLocationInParent, this, parent, pointInPose), + 100000); +} + +void +TTracker::SelectPoseAtLocationInParent(node_ref parent, BPoint pointInPose) +{ + AutoLock lock(&fWindowList); + BContainerWindow *parentWindow = FindContainerWindow(&parent); + if (parentWindow) { + AutoLock lock(parentWindow); + parentWindow->PoseView()->SelectPoseAtLocation(pointInPose); + } +} + +bool +TTracker::CloseParentWaitingForChild(const entry_ref *child, + const node_ref *parent) +{ + AutoLock lock(&fWindowList); + + BContainerWindow *parentWindow = FindContainerWindow(parent); + if (!parentWindow) + // parent window already closed, give up + return true; + + // If child is a symbolic link, dereference it, so that + // FindContainerWindow will succeed. + BEntry entry(child, true); + entry_ref resolvedChild; + if (entry.GetRef(&resolvedChild) != B_OK) + resolvedChild = *child; + + BContainerWindow *window = FindContainerWindow(&resolvedChild); + if (window) { + AutoLock lock(window); + if (!window->IsHidden()) + return CloseParentWindowCommon(parentWindow); + } + return false; +} + +void +TTracker::CloseParent(node_ref parent) +{ + AutoLock lock(&fWindowList); + if (!lock) + return; + + CloseParentWindowCommon(FindContainerWindow(&parent)); +} + +void +TTracker::ShowSettingsWindow() +{ + if (!fSettingsWindow) { + fSettingsWindow = new TrackerSettingsWindow(); + fSettingsWindow->Show(); + } else { + if (fSettingsWindow->Lock()) { + if (fSettingsWindow->IsHidden()) + fSettingsWindow->Show(); + else + fSettingsWindow->Activate(); + fSettingsWindow->Unlock(); + } + } +} + +bool +TTracker::CloseParentWindowCommon(BContainerWindow *window) +{ + ASSERT(fWindowList.IsLocked()); + + if (dynamic_cast(window)) + // don't close the destop + return false; + + window->PostMessage(B_QUIT_REQUESTED); + return true; +} + +bool +TTracker::SelectChildInParent(const entry_ref *parent, const node_ref *child) +{ + AutoLock lock(&fWindowList); + + BContainerWindow *window = FindContainerWindow(parent); + if (!window) + // parent window already closed, give up + return false; + + AutoLock windowLock(window); + + if (windowLock.IsLocked()) { + BPoseView *view = window->PoseView(); + int32 index; + BPose *pose = view->FindPose(child, &index); + if (pose) { + view->SelectPose(pose, index); + return true; + } + } + return false; +} + +const int32 kNodeMonitorBumpValue = 512; + +status_t +TTracker::NeedMoreNodeMonitors() +{ + fNodeMonitorCount += kNodeMonitorBumpValue; + PRINT(("bumping nodeMonitorCount to %d\n", fNodeMonitorCount)); + + return _kset_mon_limit_(fNodeMonitorCount); +} + +status_t +TTracker::WatchNode(const node_ref *node, uint32 flags, + BMessenger target) +{ + status_t result = watch_node(node, flags, target); + + if (result == B_OK || result != ENOMEM) + // need to make sure this uses the same error value as + // the node monitor code + return result; + + PRINT(("failed to start monitoring, trying to allocate more " + "node monitors\n")); + + TTracker *tracker = dynamic_cast(be_app); + if (!tracker) + // we are the file panel only, just fail + return result; + + result = tracker->NeedMoreNodeMonitors(); + + if (result != B_OK) { + PRINT(("failed to allocate more node monitors, %s\n", + strerror(result))); + return result; + } + + // try again, this time with more node monitors + return watch_node(node, flags, target); +} + + +AutoMounter * +TTracker::AutoMounterLoop() +{ + return fAutoMounter; +} + + +bool +TTracker::InTrashNode(const entry_ref *node) const +{ + return FSInTrashDir(node); +} + + +bool +TTracker::TrashFull() const +{ + return fTrashWatcher->CheckTrashDirs(); +} + + +bool +TTracker::IsTrashNode(const node_ref *node) const +{ + return fTrashWatcher->IsTrashNode(node); +} + diff --git a/src/kits/tracker/Tracker.h b/src/kits/tracker/Tracker.h new file mode 100644 index 0000000000..d9a462367f --- /dev/null +++ b/src/kits/tracker/Tracker.h @@ -0,0 +1,243 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef _TRACKER_H +#define _TRACKER_H + +#include +#include +#include + +#include "LockingList.h" +#include "SettingsHandler.h" +#include "Utilities.h" + +#include "tracker_private.h" + +namespace BPrivate { + +class AutoMounter; +class BClipboardRefsWatcher; +class BContainerWindow; +class BDeskWindow; +class BInfoWindow; +class BTrashWatcher; +class ExtraAttributeLazyInstaller; +class MimeTypeList; +class Model; +class BooleanValueSetting; +class ScalarValueSetting; +class HexScalarValueSetting; +class TaskLoop; +class TrackerSettingsWindow; + +typedef LockingList WindowList; + // this is because MW can't handle nested templates + + +const uint32 kNextSpecifier = 'snxt'; +const uint32 kPreviousSpecifier = 'sprv'; +const uint32 B_ENTRY_SPECIFIER = 'sref'; + +#define kPropertyEntry "Entry" +#define kPropertySelection "Selection" + + + +class TTracker : public BApplication { + public: + TTracker(); + virtual ~TTracker(); + + // BApplication overrides + virtual void Quit(); + virtual bool QuitRequested(); + virtual void ReadyToRun(); + virtual void MessageReceived(BMessage *); + virtual void Pulse(); + virtual void RefsReceived(BMessage *); + virtual void ArgvReceived(int32 argc, char **argv); + + MimeTypeList *MimeTypes() const; + // list of mime types that have a description and do not have + // themselves as a preferred handler (case of applications) + + bool TrashFull() const; + bool IsTrashNode(const node_ref *) const; + bool InTrashNode(const entry_ref *) const; + + void CloseParentWaitingForChildSoon(const entry_ref *child, + const node_ref *parent); + // closes parent, waits for child to open first + + void SelectChildInParentSoon(const entry_ref *child, + const node_ref *parent); + // waits till child shows up in parent and selects it + + void SelectPoseAtLocationSoon(node_ref parent, BPoint location); + // Used to select next item when deleting in list view mode + + enum OpenSelector { + kOpen, + kOpenWith, + kRunOpenWithWindow + }; + + bool EntryHasWindowOpen(const entry_ref *); + // return true if there is an open window for an entry + + status_t NeedMoreNodeMonitors(); + // call if ran out of node monitors to allocate more + // return false if already using all we can get + static status_t WatchNode(const node_ref *, uint32 flags, + BMessenger target); + // cover call for watch_node; if first watch_node fails, + // tries bumping the node monitor limit and calls watch_node + // again + + TaskLoop *MainTaskLoop() const; + AutoMounter *AutoMounterLoop(); + + bool QueryActiveForDevice(dev_t); + void CloseActiveQueryWindows(dev_t); + + void SaveAllPoseLocations(); + + void CloseParent(node_ref closeThis); + + void ShowSettingsWindow(); + + BContainerWindow *FindContainerWindow(const node_ref *, int32 number = 0) const; + BContainerWindow *FindContainerWindow(const entry_ref *, int32 number = 0) const; + BContainerWindow *FindParentContainerWindow(const entry_ref *) const; + // right now works just on plain windows, not on query windows + + BClipboardRefsWatcher *ClipboardRefsWatcher() const; + + protected: + // scripting + virtual BHandler *ResolveSpecifier(BMessage *, int32, BMessage *, + int32, const char *); + virtual status_t GetSupportedSuites(BMessage *); + + bool HandleScriptingMessage(BMessage *); + + bool ExecuteProperty(BMessage *, int32, const char *, BMessage *); + bool CreateProperty(BMessage *, BMessage *, int32, const char *, + BMessage *); + bool DeleteProperty(BMessage *, int32, + const char *, BMessage *); + bool CountProperty(BMessage *, int32, const char *, BMessage *); + bool GetProperty(BMessage *, int32, const char *, BMessage *); + bool SetProperty(BMessage *, BMessage *, int32, const char *, BMessage *); + + private: + // callbacks for ChildParentSoon calls + bool CloseParentWaitingForChild(const entry_ref *child, + const node_ref *parent); + bool LaunchAndCloseParentIfOK(const entry_ref *launchThis, + const node_ref *closeThis, const BMessage *messageToBundle); + bool SelectChildInParent(const entry_ref *child, + const node_ref *parent); + void SelectPoseAtLocationInParent(node_ref parent, BPoint location); + bool CloseParentWindowCommon(BContainerWindow *); + + void InitMimeTypes(); + bool InstallMimeIfNeeded(const char *type, int32 bitsID, + const char *shortDescription, const char *longDescription, + const char *preferredAppSignature, uint32 forceMask = 0); + // used by InitMimeTypes - checks if a metamime of a given is + // installed and if it has all the specified attributes; if not, the + // whole mime type is installed and all attributes are set; nulls can + // be passed for attributes that don't matter; returns true if anything + // had to be changed + // can be used to forcibly set a metamime attribute, even if it exists + + void InstallDefaultTemplates(); + void InstallTemporaryBackgroundImages(); + + void InstallIndices(); + void InstallIndices(dev_t); + + void CloseAllWindows(); + void CloseWindowAndChildren(const node_ref *); + void OpenInfoWindows(BMessage*); + void MoveRefsToTrash(const BMessage *); + void OpenContainerWindow(Model *, BMessage *refsList = NULL, + OpenSelector openSelector = kOpen, uint32 openFlags = 0, + bool checkAlreadyOpen = true, const BMessage *stateMessage = NULL); + // pass either a Model or a list of entries to open + + void SetDefaultPrinter(const BMessage *); + void EditQueries(const BMessage *); + + BInfoWindow *FindInfoWindow(const node_ref *) const; + + BDeskWindow *GetDeskWindow() const; + + status_t OpenRef(const entry_ref *, const node_ref *nodeToClose = NULL, + const node_ref *nodeToSelect = NULL, OpenSelector selector = kOpen, + const BMessage *messageToBundle = NULL); + + MimeTypeList *fMimeTypeList; + WindowList fWindowList; + BClipboardRefsWatcher *fClipboardRefsWatcher; + BTrashWatcher *fTrashWatcher; + AutoMounter *fAutoMounter; + TaskLoop *fTaskLoop; + int32 fNodeMonitorCount; + + TrackerSettingsWindow *fSettingsWindow; + + typedef BApplication _inherited; +}; + + +inline TaskLoop * +TTracker::MainTaskLoop() const +{ + return fTaskLoop; +} + +inline BClipboardRefsWatcher * +TTracker::ClipboardRefsWatcher() const +{ + return fClipboardRefsWatcher; +} + +} // namespace BPrivate + +using namespace BPrivate; + +#endif /* _TRACKER_H */ diff --git a/src/kits/tracker/Tracker.rsrc b/src/kits/tracker/Tracker.rsrc new file mode 100644 index 0000000000..9daa908a1e Binary files /dev/null and b/src/kits/tracker/Tracker.rsrc differ diff --git a/src/kits/tracker/TrackerIcons.h b/src/kits/tracker/TrackerIcons.h new file mode 100644 index 0000000000..9ea0661d75 --- /dev/null +++ b/src/kits/tracker/TrackerIcons.h @@ -0,0 +1,106 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +enum { + R_AppIcon = 1000, + R_FileIcon = 1001, + R_TrashIcon = 1003, + R_TrashFullIcon = 1004, + R_PrinterIcon = 1007, + R_FloppyIcon = 1011, + R_CDIcon = 1013, + R_BeBoxIcon = 1014, + R_BookmarkIcon = 1015, + R_PersonIcon = 1016, + R_BrokenLinkIcon = 1017, + R_DeskIcon = 1018, + R_HomeDirIcon = 1019, + R_BeosFolderIcon = 1020, + R_BootVolumeIcon = 1021, + R_FontDirIcon = 1022, + R_AppsDirIcon = 1023, + R_PrefsDirIcon = 1024, + R_MailDirIcon = 1025, + R_QueryDirIcon = 1026, + R_SpoolFileIcon = 1027, + R_GenericPrinterIcon = 1028, + R_DevelopDirIcon = 1029, + R_DownloadDirIcon = 1030, + R_PersonDirIcon = 1031, + R_UtilDirIcon = 1032, + R_ConfigDirIcon = 1033, + R_MICN_AppIcon = 1000, + R_MICN_FileIcon = 1001, + R_MICN_TrashIcon = 1003, + R_MICN_TrashFullIcon = 1004, + R_MICN_PrinterIcon = 1007, + R_MICN_FloppyIcon = 1011, + R_MICN_CDIcon = 1013, + R_MICN_BeBoxIcon = 1014, + R_MICN_BookmarkIcon = 1015, + R_MICN_PersonIcon = 1016, + R_MICN_BrokenLinkIcon = 1017, + R_MICN_DeskIcon = 1018, + R_MICN_HomeDirIcon = 1019, + R_MICN_BeosFolderIcon = 1020, + R_MICN_BootVolumeIcon = 1021, + R_MICN_FontDirIcon = 1022, + R_MICN_AppsDirIcon = 1023, + R_MICN_PrefsDirIcon = 1024, + R_MICN_MailDirIcon = 1025, + R_MICN_QueryDirIcon = 1026, + R_MICN_SpoolFileIcon = 1027, + R_MICN_GenericPrinterIcon = 1028, + R_MICN_DevelopDirIcon = 1029, + R_MICN_DownloadDirIcon = 1030, + R_MICN_PersonDirIcon = 1031, + R_MICN_UtilDirIcon = 1032, + R_MICN_ConfigDirIcon = 1033, + R_MSGG_RedBarberPoleBits = 1037, + R_BarberPoleBitmap = 1038, + R_MoveStatusBitmap = 1039, + R_CopyStatusBitmap = 1040, + R_TrashStatusBitmap = 1041, + R_ResBackNavActive = 1042, + R_ResBackNavInactive = 1043, + R_ResForwNavActive = 1044, + R_ResForwNavInactive = 1045, + R_ResUpNavActive = 1046, + R_ResUpNavInactive = 1047, + R_ResBackNavActiveSel = 1048, + R_ResForwNavActiveSel = 1049, + R_ResUpNavActiveSel = 1050, + R_ShareIcon = 1051, + R_MICN_ShareIcon = 1051 +}; diff --git a/src/kits/tracker/TrackerIcons.rdef b/src/kits/tracker/TrackerIcons.rdef new file mode 100644 index 0000000000..528abca584 --- /dev/null +++ b/src/kits/tracker/TrackerIcons.rdef @@ -0,0 +1,2113 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include "TrackerIcons.h" + +resource(1, "BEOS:L:application/x-vnd.Be-directory") #'ICON' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF00D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF0000FFFF0084D8D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF003F000000848484D8D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF003F3F3F0000ABA48484D8D80000FFFFFF000000FFFFFFFFFFFFFF" + $"FFFFFFFFFF003F3F3F3F3F0000ABA48484D8D8000000D8D8D80000FFFFFFFFFF" + $"FFFFFFFFFF003F3F3F3F3F3F3F0000ABA48484D8D8D8848484D8D80000FFFFFF" + $"FFFFFFFFFF003F3F3F3F3F3F3F3F3F0000ABA48484848484848484D8D800FFFF" + $"FF000000FF003F3F3F3F3F3F3F3F3F3F3F0000ABA4848484848484848400FFFF" + $"FF003F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000ABA484848484848400FFFF" + $"FF00D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000ABA48484848400FFFF" + $"FF00D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000AB84848400FFFF" + $"FFFF00D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F00ABAB848400FFFF" + $"FFFF00D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFF00D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D8D8D8D8D83F3F003F3F3F00ABAB8400FFFFFF" + $"FFFFFFFF00D8D8D8D8D8D8D8D8D8D8D8D8D8D8D8003F3F3F00ABA400FFFFFFFF" + $"FFFFFFFF00D8D8D8D8D8D8D8D8D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FFFFFFFF008484D8D8D8D8D8D8D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FFFFFFFFFF00008484D8D8D8D8D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FFFFFFFFFFFFFF00008484D8D8D8D8D8D8D8D8D8D8D8003F00ABA400FFFFFFFF" + $"FFFFFFFFFFFFFFFFFF00008484D8D8D8D8D8D8D8D8D8003F00AB00FFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFF00008484D8D8D8D8D8D8D8003F00AB00FFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFF00008484D8D8D8D8D8D80000AB00FFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00008484D8D8D8D80000AB00FFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00008484D8D80000AB000D0DFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00008484840000000D0D0D0DFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00008400000D0D0D0D0DFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000D0D0DFFFFFFFF" +}; + +resource(2, "BEOS:L:application/x-vnd.Be-query") #'ICON' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFF0400FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFF041B0908FFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFF0400041C1B1C0809FFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFF041B09081C1C1B1C0809FFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFF0400041C1B1C08091C1B1C1B0908FFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFF041B09081C1C1B1C08091C1B1C1B0900FFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFF0400041C1B1C08091C1B1C1B09081C1C1B0000FFFFFFFFFF" + $"FFFFFFFFFFFFFFFF041B09081C1C1B1C08091C1B1C1B09001C00D90000FFFFFF" + $"FFFFFFFFFFFF0404041C1C1B09081C1B1C1C08091B1C1C001500D1D9D900FFFF" + $"FFFFFFFFFFFF043F08091B1C1B1C09081C1B1C1C08001C001500D1D1D900FFFF" + $"FFFFFFFF0404041C3F3F08091B1C1B1C09081C1B1C0015001500D1D9AA01FFFF" + $"FFFFFFFF043F08091B1C3F3F08091B1C1B1C09001B0015000F00D9AAAA00FFFF" + $"FFFFFFFF043F3F3F08091B1C3F3F08091B1C1B00160015000FD9AAAAAA00FFFF" + $"FFFFFF00043F1B1C3F3F08091B1C3F3F08001C0015000F00D9AAAAAAAA00FFFF" + $"FFFF00D9043F1B1C1B1C3F3F08091B1C3F00150015000FD9AAAAAAAAAA000FFF" + $"FF00D9D104151A191C1B1C1C3F3F08001C0015000F00D9AAAAAAAAAAAA000FFF" + $"FF00D9D9040F15151A191C1C1B1C3F00150015000FD9AAAAAAAAAAAA000F0FFF" + $"FF008383D9D90F0F151561611C1C1B0015000F00D9AAAAAAAAAAAA000F0FFFFF" + $"FF0083838383D9D90F0F151561611C0015000FD9AAAAAAAAAAAA000F0FFFFFFF" + $"FF00838383838383D9D90F0F151561000F00D9AAAAAAAAAAAA000F0FFFFFFFFF" + $"FF008383D1D183838383D9D90F0F15000FD9AAAAAAAAAAAA000F0FFFFFFFFFFF" + $"FF008383D1AAD1D183838383D9D90F00D9AAAAAAAAAAAA000F0FFFFFFFFFFFFF" + $"FF008383D1AAAAAAD1D1838383AA0FD9AAAAAAAAAAAA000F0FFFFFFFFFFFFFFF" + $"FF008383D1AAAAAAAAAAD98383AAD9AAAAAAAAAAAA000F0FFFFFFFFFFFFFFFFF" + $"FF008383D9D9AAAAAAAAD98383AAAAAAAAAAAAAA000F0FFFFFFFFFFFFFFFFFFF" + $"FF0083838383D9D9AAAAD98383AAAAAAAAAAAA010E0FFFFFFFFFFFFFFFFFFFFF" + $"FFFF000083838383D9D9D98383AAAAAAAAAA000F0FFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFF000083838383838383AAAAAAAA000F0FFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFF00008383838383AAAAAA000F0FFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFF0000838383AAAA000F0FFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFF000083AA000F0EFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFF00000E0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" +}; + +resource(3, "BEOS:L:application/x-vnd.Be-volume") #'ICON' array { + $"FFFFFFFFFFFFFFFFFFFF00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF00D90000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFF00D93FD93F0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFF00D93FD93FD93FD90000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFF00D93FD93FD93FD93FD93F0000FFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF00D93FD93FD93FD93FD93FD93FD90000FFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFF00D9D9D9D9D9D9D9D9D9D9D9D9D9D9D9D90101FFFFFFFFFFFFFFFFFF" + $"FFFFFF00D93FD93FD93FD93FD93FD93FD93FD93FD9D9AA00FFFFFFFFFFFFFFFF" + $"FFFF00D9D9D9D9D9D9D9D9D9D9D9D9D9D9D9D9D9D9AAAA01FFFFFFFFFFFFFFFF" + $"FF00D9D9D9D9D9D9D9D9D9D9D9D9D9D9D9D9D9D9AAAAAA01FFFFFFFFFFFFFFFF" + $"00833F3FD9D9D9D9D9D9D9D9D9D9D9D9D9D9D9AAAAAAAA01FFFFFFFFFFFFFFFF" + $"008383833F3FD9D9D9D9D9D9D9D9D9D9D9D9AAAAAAAAAA01FFFFFFFFFFFFFFFF" + $"0083838383833F3FD9D9D9D9D9D9D9D9D9AAAAAAAAAAAA01FFFFFFFFFFFFFFFF" + $"0083D1D1838383833F3FD9D9D9D9D9D9AAAAAAAAAAAAAA00FFFFFFFFFFFFFFFF" + $"0083D1D9D1D1838383833F3FD9D93FAAAAAAAAAAAAAAAA00FFFFFFFFFFFFFFFF" + $"0083D1D9D9D9D1D1838383833F3FAAAAAAAAAAAAAAAAAA00FFFFFFFFFFFFFFFF" + $"0083D1D98383D9D9D1D1838383AAAAAAAAAAAAAAAAAAAA01FFFFFFFFFFFFFFFF" + $"0083D1D983000083D9D9D1D183AAAAAAAAAAAAAAAAAAAA000FFFFFFFFFFFFFFF" + $"0083D1D983AAAA000083D9D183AAAAAAAAAAAAAAAAAAAA000F0F0FFFFFFFFFFF" + $"0083D1D183838383AA8383D183AAAAAAAAAAAAAAAAAAAA010F0F0F0E0FFFFFFF" + $"0083D1D9D1D18383838383D183AAAAAAAAAAAAAAAAAAAA010E0F0F0F0F0F0FFF" + $"0083D1D9D9D9D1D1838383D183AAAAAAAAAAAAAAAAAAAA010E0F0F0F0F0F0FFF" + $"0083D1D98383D9D9D1D183D183AAAAAAAAAAAAAAAAAA010E0F0F0F0F0F0FFFFF" + $"0083D1D983000083D9D9D1D183AAAAAAAAAAAAAAAA000F0F0F0F0F0F0FFFFFFF" + $"0083D1D183AAAA000083D9D183AAAAAAAAAAAAAA000F0F0F0F0F0E0FFFFFFFFF" + $"00008383D1D18383AA8383D183AAAAAAAAAAAA000F0F0F0F0F0F0FFFFFFFFFFF" + $"FFFF00008383D1D1838383D183AAAAAAAAAA000F0F0F0F0F0F0EFFFFFFFFFFFF" + $"FFFFFFFF00008383D1D183D183AAAAAAAA000F0F0F0F0E0F0FFFFFFFFFFFFFFF" + $"FFFFFFFFFFFF00008383D1D183AAAAAA000F0F0F0E0F0F0FFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFF0000838383AAAA000F0F0F0E0F0F0FFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFF000083AA000F0E0F0F0F0F0FFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFF00000E0F0F0F0F0F0FFFFFFFFFFFFFFFFFFFFFFF" +}; + +resource(4, "BEOS:L:application/x-vnd.be-querytemplate") #'ICON' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000FFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFF00D9D9D90000FFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFF00D9D7AAAAD9D90000FFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFF00D9D7D7AAAAAAAAD9D90000FFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFF00D9D7D7D7AAAAAAAAAAAAD9D90000FFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFF00D9D7D7D7D7AAAAAAAAAAAAAAAAD9D90000FFFFFFFF" + $"FFFFFFFFFFFFFFFFFF00D9D7D7D7D7D7AAAAAAAAAAAAAAAAAAAAD9D900FFFFFF" + $"FFFFFFFFFFFFFFFF00D9D7D7D7D7D7D7AAAAAAAAAAAAAAAAAAAAAAD900FFFFFF" + $"FFFFFFFFFFFFFF00D9D7D7D7D7D7D7D7D1D1AAAAAAAAAAAAAAAAD9AA01FFFFFF" + $"FFFFFFFFFFFF00D9D7D7D7D7D7D7D7D1D1D1D1D1AAAA0000AAD9AAAA00FFFFFF" + $"FFFFFFFFFF00D9D7D7D7D7D7D7D7D1D1D1D1D1D1D1003F3F00AAAAAA00FFFFFF" + $"FFFFFFFF00D9D7D7D7D7D7D7D7D1D1D1D1D1D1D1003F1C1C00AAAAAA00FFFFFF" + $"FFFFFF00D9D7D7D7D7D7D7D7D1D1D1D1D1D1D1003F3F1C1C1A00AAAA000FFFFF" + $"FF0000D9D7D7D7D7D7D7D7D1D1D1D1D1D1D1003F3F3F1D1C1A00AAAA000FFFFF" + $"00D9D9D7D7D7D7D7D7D7D1D1D1D1D1D1D1003F3F3F1D17171C1A00000F0FFFFF" + $"008383D9D9D7D7D7D7D1D1D1D1D1D1D1003F3F3F1D17171C1C1A000F0FFFFFFF" + $"0083838383D9D9D7D1D1D1D1D1D1D1003F3F3F3F171D1C1C1C1C1A00FFFFFFFF" + $"00838383838383D9D9D1D1D1D1D1D1003F3F3F17171D1C17000017000FFFFFFF" + $"008383D1D183838383D9D9D1D1D1D1D1003F3F1D1D1700003F3F000F0FFFFFFF" + $"008383D1AAD1D183838383D9D9D1D1D9003F1C1700003F3F1C1C1A00FFFFFFFF" + $"008383D1AAAAAAD1D1838383AAD1D9AAAA0000003F3F1D17171C1A00FFFFFFFF" + $"008383D1AAAAAAAAAAD98383AAD9AAAA00003F3F3F3F17171C1C1C1A00FFFFFF" + $"008383D9D9AAAAAAAAD98383AAAAAA003F3F3F3F17171C1C1C1C1C1A00FFFFFF" + $"0083838383D9D9AAAAD98383AAAAAA003F3F17173F3F1D1C1C17171C1A00FFFF" + $"FF000083838383D9D9D98383AAAAAAAA003F3F3F3F1D1C17171C1C1A1A000FFF" + $"FFFFFF000083838383838383AAAAAAAA003F3F3F3F1717171C1A1A00000F0FFF" + $"FFFFFFFFFF00008383838383AAAAAA000F003F3F171D1C1A1A00000F0F0FFFFF" + $"FFFFFFFFFFFFFF0000838383AAAA000F0F003F3F1D1D1A00000F0F0FFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF000083AA000F0FFFFF003F1D00000F0F0FFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFF00000E0FFFFFFFFF00000F0F0FFFFFFFFFFFFFFFFF" +}; + +resource(6, "BEOS:L:application/x-vnd.be-symlink") #'ICON' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000FFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF005A5A0000FFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF005A5A5A5A0000FFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF005AA35A5A5A5A5A0000FFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00A3A35A5A5A5A5A5A5AEB00FFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00A35A3F1EA35A5A5A2D2D01FFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00A33F2D5A5A5A5A2D2D2D00FFFF" + $"00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00A33F2D5A5A1F2D2D2D2D29FFFF" + $"0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00A3A33FA32D5A5A2D2D2D2D01FFFF" + $"003F00FFFFFFFFFFFFFFFFFFFFFFFFFF005A5A5A5A5A5A5A5A2D2D2D2D00FFFF" + $"00863F00FFFFFFFFFFFFFFFFFFFFFF00A33F5A5A5AA35A1E2D2D2D2D2D01FFFF" + $"0086863F00000000FFFFFFFFFFFF00A3A3A33F1E5AA35A5A2D2D2D2D2D00FFFF" + $"008686863F1E60600000FFFFFFFF00A3A3A3A3A35A3F1E2D2D2D2D2D2D00FFFF" + $"0086868686863F60D5D500FFFFFF00A3A3A3A3A33FA3A32D2D2D2D2D2D001111" + $"008686868686D5D5D5D5D500FFFF00A3A3A3A3A3A3A3A32D2D2D2D2D00111111" + $"008686868686D5D5D5D5D500FFFF0000A3A3A3A3A3A3A32D2D2D2D0011111111" + $"008686868686D5D5D5D5D50011FFFFFF0000A3A3A3A3A32D2D2D0011111111FF" + $"008686868686D5D5D5D5D5000011FFFFFFFF0000A3A3A32D2D0011111111FFFF" + $"FF0086868686D5D5D5D5D5D5D50011FFFFFFFFFF0000A32D0011111111FFFFFF" + $"FFFF00868686D5D5D5D5D50000111100000011FFFFFF000011111111FFFFFFFF" + $"FFFFFF008686D5D5D50000111111FF003F5D0011FFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFF0086D50000111111FFFFFF003F5D000011FFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF0000111111FFFFFFFF00F9F9F93F1E000011FFFFFFFFFFFFFFFFFF" + $"FFFFFF00FFFFFFFFFFFFFFFFFF00FAFAFAFAF9F95D5D0011FFFFFFFFFFFFFFFF" + $"FF00000011FFFFFFFF0000FF00F9F9FA3F000000000011FFFFFFFF0000000011" + $"0086D50011FFFFFFFF00F9003F000000001111FFFFFFFF0000000000F9F90011" + $"FF000011FFFFFFFFFF0000FF00FFFFFFFFFFFFFFFFFFFF00F93FFAFAF95D0011" + $"FFFF0011FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF003F1E3F1E0011FF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000011FF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" +}; + +resource(R_AppIcon) #'ICON' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFF00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFF00FA0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFF00FAFAFAFA0000FFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFF00FAFAFAFAFAFAFA0000FFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFF00FAFAFAFAFAFAFAFAFAFA0000FFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF00FAFAFAFAFAFAFAFAFAFAFAFA5D00FFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFF00FAFAFAFAFAFAFAFAFAFAFAFA5D5D00FFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFF001F1FFAFAFAFAFAFAFAFAFA5D5D5D00FFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFF00F9F91F1FFAFAFAFAFAFA5D5D5D5D00FFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFF00F9F9F9F91F1FFAFAFA5D5D5D5D5D00FFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFF00FF00F9F9F9F9F9F91F1F5D5D5D5D5D5D00FFFFFFFFFFFFFFFF" + $"FFFFFFFFFF00600100F9F9F9F9F9F9F9F95D5D5D5D5D5D00FFFFFFFFFFFFFFFF" + $"FFFFFFFF0060606001F9F9F9F9F9F9F9F95D5D5D5D5D5D0100FFFFFFFFFFFFFF" + $"FFFFFF006060606001F9F9F9F9F9F9F9F95D5D5D5D5D5D01A30000FFFFFFFFFF" + $"FFFF00606060606001F9F9F9F9F9F9F9F95D5D5D5D5D5D01A3A3A30000FFFFFF" + $"FF0060606060601B29F9F9F9F9F9F9F9F95D5D5D5D5D01A3A3A3A3A32E00FFFF" + $"0060606060601B600000F9F9F9F9F9F9F95D5D5D5D00A3A3A3A3A32D2E01FFFF" + $"001F1F60606060601B600000F9F9F9F9F95D5D5D00A3A3A35A5A2D2E2D00FFFF" + $"0086861F1F60606060601B262800F9F9F95D5D00A3A3A35A5A2D2E2D2D29FFFF" + $"00868686861F1F606060D5D528270100F95D005A5AA35A5A2D2E2D2D2E01FFFF" + $"008686868686861F1FD5D5D5272800CA0100CAA3A31F1F2D2D2E2D2D2E00FFFF" + $"00868686868686868608D4D5282800CACACAA3A3A3A3A32D2D2E2D2D2E01FFFF" + $"00868686868686868608D4D5D52800CACAA3A3A3A3A3A32D2D2E2D2D2E00FFFF" + $"00868686868686868608D4D5D52800CAA3A3A3A3A3A3A32D2D2E2D2D2E00FFFF" + $"00868686868686868608D4D5D5D500A3A3A3A3A3A3A3A32D2D2E2D2D2E001111" + $"008686868686868686D5D5D5D5D501A3A3A3A3A3A3A3A32D2D2D2E2D00111111" + $"000086868686868686D5D5D5D5010000A3A3A3A3A3A3A32D2D2E2D0011111111" + $"FFFF00008686868686D5D5D5011111110000A3A3A3A3A32D2D2E0011111111FF" + $"FFFFFFFF0000868686D5D5011111111111110000A3A3A32D2E0011111111FFFF" + $"FFFFFFFFFFFF000086D5011111111111FFFFFFFF0000A32D0011111111FFFFFF" + $"FFFFFFFFFFFFFFFF00001111111111FFFFFFFFFFFFFF000011111111FFFFFFFF" +}; + +resource(R_FileIcon) #'ICON' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFF0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFF0060600000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFF0060606060600000FFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF00603F603F603F603F0000FFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFF00603F603F603F603F603F600000FFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFF00603F3F3F3F3F3F3F3F3F3F3F3F3F0000FFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFF00603F603F603F603F603F603F603F603F3F0000FFFFFFFFFFFF" + $"FFFFFFFFFF00603F3F3F3F3F3F3F3F3F3F3F3F3F3F3F3F3F3F3F0000FFFFFFFF" + $"FFFFFFFF00603F3F3F3F3F3F3F3F3F3F3F3F3F3F3F3F3F3F3F3F3F3F0000FFFF" + $"FFFFFF00603F3F3F3F3F3F3F3F3F3F3F3F3F3F3F3F3F3F3F3F3F3F3F603F00FF" + $"FFFF00603F603F603F603F603F603F603F603F603F603F603F603F603F00FFFF" + $"FF00603F603F603F603F603F603F603F603F603F603F603F603F603F00AEAEAE" + $"00003F603F603F603F603F603F603F603F603F603F603F603F603F0000AEAEFF" + $"FFAE0000603F603F603F603F603F603F603F603F603F603F603F00AE8700AEFF" + $"FFFFAE0000006060606060606060606060601B60606060606000AE87870100AE" + $"FFFFFFAE00AE00006060606060606060606060601B60606000AE870000AEAEAE" + $"FFFFFFFFAE00AEAE00006087608760876087608760871B29AE0000AEAEFFFFFF" + $"FFFFFFFFFFAE0087AEAE000087878787878787878787010000AEAEFFFFFFFFFF" + $"FFFFFFFFFFFFAE008787AEAE0000878787878787870100AEAEFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFAE00608787AEAE0000AEAEAEAE00AEAEFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFAE0060608787AEAE0000AE00AEAEFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFAE00606060870000AE00AEAEFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFAE00600000AEAEAEAEAEFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFAE00AEAEFFFFFFAEFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFAEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" +}; + +resource(R_TrashIcon) #'ICON' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFF0002080F110000FFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFF000002080F110F0F0000FFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFF00000002080F110F0F0F0F0000FFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF0000000002080F110F0F0F0F0E0F0000FFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFF000000000002080F110F0F0F0F0E0F0F0F0000FFFFFFFFFF" + $"FFFFFFFFFFFFFF00000000000002080F110F0F0F0F0E0F0F0F0F0F0000FFFFFF" + $"FFFFFFFFFFFF0000000000000002080F110F0F0F0F0E0F0F0F0F170F00FFFFFF" + $"FFFFFFFFFF000000000000000002080F110F0F0F0F0E0F0F0F170F0500FFFFFF" + $"FFFFFFFF00000000000000000002080F110F0F0F0F0E0F0F180E0500FFFFFFFF" + $"FFFFFF0017170000000000000002090F110F0F0E0F0F0F170F050400FFFFFFFF" + $"FFFFFF000E0F1817000000000002090F110E0F0F0F0F170F040000FFFFFFFFFF" + $"FFFFFF0004040F0F171800000002080F110F0F0F0F170F0400B500FFFFFFFFFF" + $"FFFFFFFF000004040F0F17180002080F110F0F0F170F040400B500FFFFFFFFFF" + $"FFFFFFFF00B5000004040F0F1718080F110F0F170F040000B5B500FFFFFFFFFF" + $"FFFFFFFFFF00B5B5000004040F0F1718110E180F0400B5B5B500FFFFFFFFFFFF" + $"FFFFFFFFFF00B5B5B500040404050E0F18170F040400B5B5B500FFFFFFFFFFFF" + $"FFFFFFFFFF006E6EB5B500000000040F0F0F040000B5B5B5B500FFFFFFFFFFFF" + $"FFFFFFFFFFFF006E6E6EB5B5B5B50004040500B5B5B5B5B5B500FFFFFFFFFFFF" + $"FFFFFFFFFFFF006E6E6E6E6EB5B5B5000000B5B5B5B5B5B500FFFFFFFFFFFFFF" + $"FFFFFFFFFFFF006E6E6E6E6E6E6EB5B56EB5B5B5B5B5B5B501FFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFF006E6E6E6E6E6E6E6E6EB5B5B5B5B5B5B501FFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFF006E6E6E6E6E6E6E6E41B5B5B5B5B5B5000A0B0BFFFFFFFFFF" + $"FFFFFFFFFFFFFF006E6E6E6E6E6E6E6E41B5B5B5B5B5B5000A0B0B0A0BFFFFFF" + $"FFFFFFFFFFFFFFFF006E6E6E6E6E6E6E41B5B5B5B5B5B5000A0B0B0A0BFFFFFF" + $"FFFFFFFFFFFFFFFF006E6E6E6E6E6E6E41B5B5B5B5B5000A0B0B0A0BFFFFFFFF" + $"FFFFFFFFFFFFFFFF006E6E6E6E6E6E6E41B5B5B5B5B5000A0B0B0AFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF006E6E6E6E6E6E41B5B5B5B5000A0B0B0AFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFF00006E6E6E6E41B5B5B5000A0B0B0AFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFF00006E6E41B5B5000A0B0B0AFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFF000041B5000A0B0B0AFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000A0B0A0BFFFFFFFFFFFFFFFFFFFF" +}; + +resource(R_TrashFullIcon) #'ICON' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFF00FFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFF0002080F11000000FE00FFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFF000002080F111100FEFD00FFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFF00008200000F1100FEFEFEFD00FFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFF0000FF0000828282820000FEFEB0FEFDFD0000FFFFFFFFFFFFFF" + $"FFFFFFFF0000FEFEB0003F828282823F2D00B0B0B0FDFC000E0000FFFFFFFFFF" + $"FFFF0000FEFEFE626200A33F3F823F2D2E008989B0FDFCFA000E0F0000FFFFFF" + $"FF00FEFEFE626262B000A3A3A33F2D2E2D00B0B0B0B0FCFAFA00170F00FFFFFF" + $"FFFF00FEFEFEB089B000A3A3A3A32D2E2D00B08989B0FCFA00170F0400FFFFFF" + $"FFFFFF00FDFDFDB0B000A3A3A3A32D2E2D008989B0FDFC0000000400FFFFFFFF" + $"FFFFFF0000FDFCFCFCFC00A3A3A32D2E0000B0B0FEFD00170F040400FFFFFFFF" + $"FFFFFF000E00FAFAFAFA000000A32D00D700B089FE00170F040000FFFFFFFFFF" + $"FFFFFF00040400FA00000E0F000000D7D7B0008900180F0400B500FFFFFFFFFF" + $"FFFFFFFF000004000F0F1717B000D7D7B0B00000180F040400B500FFFFFFFFFF" + $"FFFFFFFF00B5000004040F0F17180000898900170F040000B5B500FFFFFFFFFF" + $"FFFFFFFFFF00B5B5000004040F0F17180000170F0400B5B5B500FFFFFFFFFFFF" + $"FFFFFFFFFF00B5B5B500040404050E0F18170F040400B5B5B500FFFFFFFFFFFF" + $"FFFFFFFFFF006E6EB5B500000000040F0F0F040000B5B5B5B500FFFFFFFFFFFF" + $"FFFFFFFFFFFF006E6E6EB5B5B5B50004040500B5B5B5B5B5B500FFFFFFFFFFFF" + $"FFFFFFFFFFFF006E6E6E6E6EB5B5B5000000B5B5B5B5B5B500FFFFFFFFFFFFFF" + $"FFFFFFFFFFFF006E6E6E6E6E6E6EB5B56EB5B5B5B5B5B5B501FFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFF006E6E6E6E6E6E6E6E6EB5B5B5B5B5B5B501FFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFF006E6E6E6E6E6E6E6E41B5B5B5B5B5B5000A0B0BFFFFFFFFFF" + $"FFFFFFFFFFFFFF006E6E6E6E6E6E6E6E41B5B5B5B5B5B5000A0B0B0A0BFFFFFF" + $"FFFFFFFFFFFFFFFF006E6E6E6E6E6E6E41B5B5B5B5B5B5000A0B0B0A0BFFFFFF" + $"FFFFFFFFFFFFFFFF006E6E6E6E6E6E6E41B5B5B5B5B5000A0B0B0A0BFFFFFFFF" + $"FFFFFFFFFFFFFFFF006E6E6E6E6E6E6E41B5B5B5B5B5000A0B0B0AFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF006E6E6E6E6E6E41B5B5B5B5000A0B0B0AFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFF00006E6E6E6E41B5B5B5000A0B0B0AFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFF00006E6E41B5B5000A0B0B0AFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFF000041B5000A0B0B0AFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000A0B0A0BFFFFFFFFFFFFFFFFFFFF" +}; + +resource(R_PrinterIcon) #'ICON' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFF00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFF003F0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFF003F1717180000FFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFF003F1717181717180000FFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF003F171705043F3F1717180000FFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFF003F171705040F11113F3F1717180000FFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFF003F171705040404051111113F3F1717180000FFFFFFFFFFFF" + $"FFFFFFFFFFFF003F1717050404043F3F04041111113F3F1717180000FFFFFFFF" + $"FFFFFFFFFF003F171705040404FEFEFE3F3F04041111113F3F1717180000FFFF" + $"FFFFFFFF003F17170504040405FDFEFEFEFE3F3F040411113F171718170400FF" + $"FFFFFF003F1717183F3F04046262FDFDFEFEFEFE3F3F043F17171817040500FF" + $"FFFF0011113F3F1717183F3F89896262FDFDFEFEFE3F3F1717181704050400FF" + $"FF003F171711113F3F1717183F3F89896262FDFDFEFE171718170405040000FF" + $"003F171718171711113F3F1717183F3F89896262FD17171817040504000400FF" + $"003F3F17171817171811113F3F1717183F3F893F17171817040504000404000B" + $"0011113F3F17171817171811113F3F1717183F1717181704050400040405000A" + $"00111111113F3F17171817171811113F3F171718171705040400040504000B0A" + $"001111111111113F3F17171817171811113F3F171705040400040504000A0B0B" + $"0011111111111111113F3F17171817171811113F04040400050404000B0A0BFF" + $"00111104041111111111113F3F17171817171800040400050404000B0A0BFFFF" + $"001104171804041111111111113F3F17171804000400040504000B0A0BFFFFFF" + $"00043F171718170405111111111111171705040000040405000A0B0AFFFFFFFF" + $"0011113F3F171718170405111111111104040400050404000B0A0BFFFFFFFFFF" + $"00111111113F3F171718170405111111040404050404000B0A0BFFFFFFFFFFFF" + $"FF0000111111113F3F171718170000110405040404000B0B0AFFFFFFFFFFFFFF" + $"FFFF0A0000111111113F3F170404001105040404000B0B0AFFFFFFFFFFFFFFFF" + $"FFFF0A0B0A0000111111110504040011040504000B0A0BFFFFFFFFFFFFFFFFFF" + $"FFFFFFFF0A0B0A0000111105040011110404000B0B0AFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFF0A0B0000040000001104000B0B0AFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF0A0B000A0BFF00000A0B0AFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFF0A0BFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" +}; + +resource(R_FloppyIcon) #'ICON' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFF00FFFF0E0FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFF000A00000F0F0F0FFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFF000A0B040000000F0F0F0FFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF000A0B04002D2E2D00000F0F0F0FFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFF000A0B04003F3F2D2E2DD200000E0F0F0FFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFF000A0B04003F3F3F3F3FD2D2D2D200000E0F0F0FFFFFFFFFFF" + $"FFFFFFFFFFFF000A0B04003F3F3F3F3F3F3F3FD2D2D2D200000E0F0F0FFFFFFF" + $"FFFFFFFFFF000A0B04003F3F3F3F3F3F3F3F3F3F3FD2D2001700000F0F0F0EFF" + $"FFFFFFFF000A0B0A0B1700003F3F3F3F3F3F3F3F3F3F00170B0A0B000F0F0EFF" + $"FFFFFF000A0B0A0B0B0A181700003F3F3F3F3F3F3F00170B0A0B04000F0F0FFF" + $"FFFF000A0B0A0B0B0A0B0B0A181700003F3F3F3F00170B0A0B0404000F0FFFFF" + $"FF000A0B0A0B1500000B0B0A0B0A181700003F00170B0A0B0404000F0FFFFFFF" + $"000A0B0A0B1500181700000B0A0B0B0A181700170B0B0A0405000F0EFFFFFFFF" + $"003F3F0A15001817040B1700000B0B0A0B0B170B0A0B0404000F0FFFFFFFFFFF" + $"0015153F00171705000417181700000B0A0B0A0B0B0404000F0FFFFFFFFFFFFF" + $"FF000015003F3F0004171817171817000B0A0B0B0404000F0FFFFFFFFFFFFFFF" + $"FFFFFF000015153F3F171718171700180A0B0B0404000F0FFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF000015153F3F171700180A0B0B0404000F0FFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFF000015153F00170B0A0B0404000F0FFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF000015003F3F0A0405000F0EFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFF000015150405000E0FFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFF000004000F0FFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000E0FFFFFFFFFFFFFFFFFFFFFFFFFFFFF" +}; + +resource(R_CDIcon) #'ICON' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFF0000001D1E1E1E1E1E1E1E000000FFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFF00001D1E1E191A191A1A1A1A1A1D1E1E00000E0EFFFFFFFFFFFF" + $"FFFFFFFF00003F3F3F191A191A191A1A1A1A193F3F191E1E00000E0EFFFFFFFF" + $"FFFFFF001D1E3F3F3F3F191A191A1A1A1A191A3F191A193F1E1E000E0EFFFFFF" + $"FFFF001D1A191A3F3F3F3F191414141414143F3F193F3F1A191A1D000F0EFFFF" + $"FFFF001D1A191A191A3F14141E1E1E1D1E1E14143F1A191A191A1E000E0FFFFF" + $"FF0060604141191A191A141E1D090000081E3F14191A191A41416060000F0FFF" + $"FF00606041414444D8141E1E000F0F0F0F081E1E14D8444441416060000F0FFF" + $"FF00606041414444D8141E1D000FFFFFFF001E1D14D8444441416060000E0FFF" + $"FF0060604141191A191A141E1D090000081E1E1419191A1941416060000F0FFF" + $"FFFF001A191A191A191A14141E1E1E1D1E1E14143F1919191A191A000F0F0FFF" + $"FFFF001D1D1A191A1A3F193F141414141414193F3F3F3F19193F1D000F0FFFFF" + $"FFFFFF001D1E1A3F3F1A193F3F1A191919191A193F3F3F3F1D1E000F0F0FFFFF" + $"FFFFFFFF00003F1D1A193F3F191A191919191A191A3F3F3F00000F0F0FFFFFFF" + $"FFFFFFFFFFFF00001D1E1E191A19191919191A1D1E1E00000F0F0F0FFFFFFFFF" + $"FFFFFFFFFFFFFFFF0000001D1E1E1E1E1E1E1E0000000F0F0F0F0FFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFF00000000000000000F0F0F0F0F0FFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFF0F0F0F0F0F0F0F0F0FFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" +}; + +resource(R_BeBoxIcon) #'ICON' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFF0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFF000A040000FFFFFFFFFFFF0000FFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFF000A04002D2D0000FFFFFF0060600000FFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF000A04003F3F2D2E2D00000060600060600000FFFFFFFFFFFFFFFF" + $"FFFFFFFF000A04003F3F3F3F3FD2D2D200000060606060600000FFFFFFFFFFFF" + $"FFFFFF000A0B00003F3F3F3F3F3F3FD2000A00006060606060600100FFFFFFFF" + $"FFFF000A0B0A0B0B00003F3F3F3F3F000A0B040060606060606001600000FFFF" + $"FF000A0B0A000B0B0A0B00003F3F000A0B040400AE60606060016060AE00FFFF" + $"000A0B0A001800000A0B0B0A00000B0B040400AE00006060006060AEAE01FFFF" + $"003F0A001817041800000A0B0A0B0B040400AEAE606000006060AEAE01FFFFFF" + $"000015003F000017171800000A0B040400AE00006060606060AEAE01FFFFFFFF" + $"FFFF000015173F3F1717000B0B040400AE17181700006060AEAE000000FFFFFF" + $"FFFFFF00000015153F000A0B040000873F3F1717006060AEAE013F110E00FFFF" + $"FFFF000E0F0F0000003F3F040400000087873F006060AEAE003F110E0F00FFFF" + $"FF001B1C0F0F0E0F00000B04000F0F0F000000873FAEAE003F110E0F0F00FFFF" + $"001B1C1B1C1C0F0E0F0F00000F0F0F0F0F1B000087AE003F110E0F0F0F000F0F" + $"003F3F1B1C1B1C1C0F0E0F0F0F0F0F1B1C1C1B1C00003F110E0F0F0F000F0F0F" + $"0017173F3F1B1C1B1C1C0F0E0F0F1C1B1C1C1B1C1C3F110E0F0F0F000F0F0FFF" + $"00171718173F3F1B1C1B1C1C0F1B1C1B1C1C1B1C3F110E0F0F0F000F0F0FFFFF" + $"001717393418173F3F1B1C1B1C1C1B1C1C1B1C3F110E0F0F0F000F0F0FFFFFFF" + $"00171735342B2B17173F3F1B1C1B1C1C1B1C3F110E0F0F0F000F0F0FFFFFFFFF" + $"FF000017171817171817183F3F1B1C1B1C3F0E0F0F0F0F000F0F0FFFFFFFFFFF" + $"FFFFFF000017171817171817183F3F1B3F0E0F0F0F0F000F0F0FFFFFFFFFFFFF" + $"FFFFFFFFFF000017171817171817183F170F0F0F0E000F0F0FFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFF00001717181717181B180F0F0E000F0F0FFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF0000171718171C170F0F000F0F0EFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFF000017171C170F000F0F0FFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFF00001B18000E0F0FFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000E0F0FFFFFFFFFFFFFFFFFFFFFFFFF" +}; + +resource(R_BookmarkIcon) #'ICON' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000FFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF003F0000FFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF001B1C3F3F0000FFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFF0000003F191A1B1C3F3F0000FFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFF003F3F3F3F3F3F191A1B1C3F3F0000FFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFF001B1C1B3F3F3F3F3F3F191A1B1C3F3F000000FFFF" + $"FFFFFFFFFFFFFFFFFFFF000000151B1C1C3F3F3F3F3F3F191A1B1C3F3F0000FF" + $"FFFFFFFFFFFFFFFFFFFF002B2A0000151B1C1C3F3F3F3F3F3F191A1B1C0000FF" + $"FFFFFFFFFFFFFFFFFF00302C2D2B2A0000151B1C1C3F3F3F3F1B1C00001B00FF" + $"FFFFFFFFFFFFFF00000000302F2C2D2B2B0100151C1B1C3F1B00001C0F005D00" + $"FFFFFFFFFFFF001B1C0F0F0000302F2C2D2B2B0100151C1B001C0F0F005D00FF" + $"FFFFFFFFFF001B1C1B1C1C0F0E00002F302C2D2B2A000016000F1B005D000FFF" + $"FFFFFFFF003F3F3F3F1B1C1B1C0F0F00002F302C2D2B2A00001B005D000FFFFF" + $"FFFFFFFF003F3F3F3F3F3F1B1C1B1C0F0F00002F302B313132005D000FFFFFFF" + $"FFFFFF003F3F3F3F3F3F3F3F3F1B1C1B1C0F0F00002F32313200000FFFFFFFFF" + $"FFFFFF001B3F3F3F3F3F3F3F3F3F3F1B1C1B1C1C00003231320000000F0FFFFF" + $"FFFF003F1B3F1B3F3F3F3F3F3F3F3F3F3F1B000000000031312D2B2A00000F0F" + $"FF00003F3F3F1B3F3F3F3F3F3F3F3F3F3F000E0F1C000031312C2D2C2B2B0000" + $"FF1B0000003F3F1B1C3F3F3F3F3F3F3F000E1C3F005D000000002D2C2D2D000F" + $"FF00003F3F00003F3F1B1C3F3F3F3F3F001B3F005D00000EFFFF00002D2C00FF" + $"005D5D00003F3F00003F3F1B1C3F3F001B3F005D000EFFFFFFFFFFFF000000FF" + $"FF00005D5D00003F3F00003F3F1B3F001B005D000FFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFF00005D5D00003F3F00003F001B005D000FFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF00005D5D00003F3F001B005D000FFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFF00005D5D00003F005D000EFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF00005D5D005D000EFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFF00005D000EFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFF000EFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" +}; + +resource(R_PersonIcon) #'ICON' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFF02020008FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF041B18110802FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFF041B3F3F1B150900FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFF021B3F3F1B150B00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFF04151C1B18110800FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFF02081516150B0800FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFF00F9000A0B080B00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFF003FF97D000000007D0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFF00F93F3F7D7D7D7D7D7DF900FFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFF00F9F9F93F3FF9F9F9F97D00FFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFF00F9F9F9F9F93F3FF97D7D00FFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFF00F9F9F9F9F9F9F97D7D7D00FFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFF00F9F9F9F9F9F9F97D7D7D00FFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFF00F9F9F9F9F9F9F97D7D7D00FFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFF00F9F9F9F9F9F9F97D7D7D00FFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFF00F9F9F9F9F9F9F97D7D7D00FFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFF00F9F9F9F9F9F97D7DC500FFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFF00F9F9F9F9F9F97DC500FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFF00F9F9C5F9F9F9C5C500FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFF00F9F9C5F9F9C5C57D000AFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFF00F9F9C5F9F97D7D7D000A0B0AFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFF00F9F9C5F9F97D7D7D000A0B0A0B0BFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFF00F9F9C5F9F97D7D7D000A0B0A0B0B0A0BFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFF00F9F9C5F9F97D7D7D000A0B0A0B0B0A0B0B0AFFFFFFFFFFFF" + $"FFFFFFFFFFFFFF00F9F9C5F9F97D7D7D000A0B0A0B0B0A0B0B0AFFFFFFFFFFFF" + $"FFFFFFFFFFFFFF00F9F9C5F9F97D7D7D000A0B0A0B0B0A0BFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFF0000F9C5F9F97D7D7D000A0B0A0B0BFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF0000F9F97D7D000A0B0A0BFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFF000000000A0B0A0BFFFFFFFFFFFFFFFFFFFFFFFFFF" +}; + +resource(R_BrokenLinkIcon) #'ICON' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000FFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF005A5A0000FFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF005A5A5A5A0000FFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF005AA35A5A5A5A5A0000FFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00A3A35A5A5A5A5A5A5AEB00FFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00A35A3F1EA35A5A5A2D2D01FFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00A33F2D5A5A5A5A2D2D2D00FFFF" + $"00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00A33F2D5A5A1F2D2D2D2D29FFFF" + $"0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00A3A33FA32D5A5A2D2D2D2D01FFFF" + $"003F00FFFFFFFFFFFFFFFFFFFFFFFFFF005A5A5A5A5A5A5A5A2D2D2D2D00FFFF" + $"00863F00FFFFFFFFFFFFFFFFFFFFFF00A33F5A5A5AA35A1E2D2D2D2D2D01FFFF" + $"0086863F00000000FFFFFFFFFFFF00A3A3A33F1E5AA35A5A2D2D2D2D2D00FFFF" + $"008686863F1E60600000FFFFFFFF00A3A3A3A3A35A3F1E2D2D2D2D2D2D00FFFF" + $"0086868686863F60D5D500FFFFFF00A3A3A3A3A33FA3A32D2D2D2D2D2D001111" + $"008686868686D5D5D5D5D500FFFF00A3A3A3A3A3A3A3A32D2D2D2D2D00111111" + $"008686868686D5D5D5D5D500FFFF0000A3A3A3A3A3A3A32D2D2D2D0011111111" + $"008686868686D5D5D5D5D50011FFFFFF0000A3A3A3A3A32D2D2D0011111111FF" + $"008686868686D5D5D5D5D5000011FFFFFFFF0000A3A3A32D2D0011111111FFFF" + $"FF0086868686D5D5D5D5D5D5D50011FFFFFFFFFF0000A32D0011111111FFFFFF" + $"FFFF00868686D5D5D5D5D50000111100000011FFFFFF000011111111FFFFFFFF" + $"FFFFFF008686D5D5D50000111111FF003F5D0011FFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFF0086D50000111111FFFFFF003F5D000011FFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF0000111111FFFFFFFF00F9F9F93F1E000011FFFFFFFFFFFFFFFFFF" + $"FFFFFF00FFFFFFFFFFFFFFFFFF00FAFAFAFAF9F95D5D0011FFFFFFFFFFFFFFFF" + $"FF00000011FFFFFFFF0000FF00F9F9FA3F000000000011FFFFFFFF0000000011" + $"0086D50011FFFFFFFF00F9003F000000001111FFFFFFFF0000000000F9F90011" + $"FF000011FFFFFFFFFF0000FF00FFFFFFFFFFFFFFFFFFFF00F93FFAFAF95D0011" + $"FFFF0011FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF003F1E3F1E0011FF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000011FF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" +}; + +resource(R_DeskIcon) #'ICON' array { + $"FFFFFFFFFFFFFFFFFFFFFF0000FFFFFFFFFF0000FFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFF00D9D90000FFFF0037360000FFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF00D9D9D9D9D9000035343736370000FFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFF00D9D9D9D9D9D9D9D9090935343737360000FFFFFFFFFFFF" + $"FFFFFFFFFFFFFF00D9D93FD9D9D9D9D9D9D9D909093534373900FFFFFFFFFFFF" + $"FFFFFFFFFFFF00D9D93F3F3F3FD9D9D9D9D9D90B0B08093A3900FFFFFFFFFFFF" + $"FFFFFFFFFF00D9D93F3F3F3F3F3F3FD9D9D9D90B1500D90900FFFFFFFFFFFFFF" + $"FFFFFFFF00D9D93F3F3F3F3F3F3F3F3F3FD9000A3F0000D9D90000FFFFFFFFFF" + $"FFFFFF00D9D93F3F3F3F1B150000000083D9013F000B1500D93FAA00FFFFFFFF" + $"FFFF00D9D93F3F3F3F3F3F3F1B838383D9D900161515008383AAAA00FFFFFFFF" + $"FF0083D9D9D983833F3F3F3F3F83D9D9D9D9830000008383D1AAD100FFFFFFFF" + $"FF00838383D9D9D983833F3F83D9D9D9D9D9D983838383D1D1D1AA00FFFFFFFF" + $"FF00D183838383D9D9D98383D9D9D9D9D9D9D9D9D93FAAD1D1AAAA00FFFFFFFF" + $"FF00AAD1D183838383D9D9D9D9D9D9D9D9D9D9D93FAAAAD1D1AAAA00FFFFFFFF" + $"FF0083AAAAD1D183838383D9D9D9D9D9D9D9D93FAAAAD1AAD1AAAA00FFFFFFFF" + $"FF0083D183AAD1D1D183838383D9D9D9D9D93FAAAAD1AAAAAAAAAA00FFFFFFFF" + $"FF0083D9D183D1AAAAD1D183838383D9D93FAAAAD1AAAAAAAAAAAA00FFFFFFFF" + $"FF0083838383D183D1AAAAD1D18383833FAAAAD1AAAAAAAAAAAAAA00FFFFFFFF" + $"FF00AAAA8383D18383D1D1AAD1D1D18383AAD1AAAAAAAAAAAAAAAA01FFFFFFFF" + $"FF008383AAAAD1D1D1838383D1AAAAD1D1D1AAAAAAAAAAAAAAAAAA01FFFFFFFF" + $"FF0083838383D1D1D1D1D183D183D1AAD9AAAAAAAAAAAAAAAAAAAA000F0FFFFF" + $"FF0083D18383D1D1D1D1D1000083D9D1D9AAAAAAAAAAAAAAAAAAAA000F0F0FFF" + $"FF0083D9D183D1D1D1D1000F00838383D9AAAAAAAAAAAAAAAAAA000F0F0FFFFF" + $"FF0083838383D1D1D1000F0F00AAAA83D9AAAAAAAAAAAAAAAA000F0F0FFFFFFF" + $"FFFF00008383D1D1000F0F0E008383AAAAAAAAAAAAAAAAAA010F0F0EFFFFFFFF" + $"FFFFFFFF0000D1000E0F0F0F00838383D9AAAAAAAAAAAA000F0F0FFFFFFFFFFF" + $"FFFFFFFFFFFF000E0F0F0F0F0083D183D9AAAAAAAAAA000F0F0FFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFF0083D9D1D9AAAAAAAA000F0F0FFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFF00838383D9AAAAAA000F0F0FFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFF00008383D9AAAA000F0F0EFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFF0000D9AA000F0E0FFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000E0F0FFFFFFFFFFFFFFFFFFFFFFF" +}; + +resource(R_HomeDirIcon) #'ICON' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF00D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF0000FFFF0084D8D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF003F000000848484D8D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF003F3F3F0000ABA48484D8D80000FFFFFF000000FFFFFFFFFFFFFF" + $"FFFFFFFFFF003F3F3F3F3F0000ABA48484D8D8000000D8D8D80000FFFFFFFFFF" + $"FFFFFFFFFF003F3F3F3F3F3F3F0000ABA48484D8D8D8848484D8D80000FFFFFF" + $"FFFFFFFFFF003F3F3F3F3F3F3F3F3F0000ABA48484848484848484D8D800FFFF" + $"FF000000FF003F3F3F3F3F3F3F3F3F3F3F0000ABA4848484848484848400FFFF" + $"FF003F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000ABA484848484848400FFFF" + $"FF00D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000ABA48484848400FFFF" + $"FF00D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000AB84848400FFFF" + $"FFFF00D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F00ABAB848400FFFF" + $"FFFF00D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFF00D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D8D8D8D8D83F3F003F3F3F00ABAB8400FFFFFF" + $"FFFFFFFF00000000D8D8D8D8D8D8D8D8D8D8D8D8003F3F3F00ABA400FFFFFFFF" + $"FFFFFFFF002DCACA00D8D8D8D8D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FFFFFF002D2D00CACA00D8D8D8D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FFFF002D2D001200CACA00D8D8D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FF002D2D00123F1200CACA00D8D8D8D8D8D8D8D8D8D8003F00ABA400FFFFFFFF" + $"FF000000123F3F3F1200000084D8D8D8D8D8D8D8D8D8003F00AB00FFFFFFFFFF" + $"FFFF00123F00003F3F120000008484D8D8D8D8D8D8D8003F00AB00FFFFFFFFFF" + $"FFFF003F3F00003F003F00FFFF00008484D8D8D8D8D8D80000AB00FFFFFFFFFF" + $"FFFF003F3F00003F3F3F00FFFFFFFF00008484D8D8D8D80000AB00FFFFFFFFFF" + $"FF0000000000000000000000FFFFFFFFFF00008484D8D80000AB000D0DFFFFFF" + $"FF0035353535353535353500FFFFFFFFFFFFFF00008484840000000D0D0D0DFF" + $"FF0000000000000000000000FFFFFFFFFFFFFFFFFF00008400000D0D0D0D0DFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000D0D0DFFFFFFFF" +}; + +resource(R_BeosFolderIcon) #'ICON' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF00D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF0000FFFF0084D8D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF003F000000848484D8D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF003F3F3F0000ABA48484D8D80000FFFFFF000000FFFFFFFFFFFFFF" + $"FFFFFFFFFF003F3F3F3F3F0000ABA48484D8D8000000D8D8D80000FFFFFFFFFF" + $"FFFFFFFFFF003F3F3F3F3F3F3F0000ABA48484D8D8D8848484D8D80000FFFFFF" + $"FFFFFFFFFF003F3F3F3F3F3F3F3F3F0000ABA48484848484848484D8D800FFFF" + $"FF000000FF003F3F3F3F3F3F3F3F3F3F3F0000ABA4848484848484848400FFFF" + $"FF003F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000ABA484848484848400FFFF" + $"FF00D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000ABA48484848400FFFF" + $"FF00D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000AB84848400FFFF" + $"FFFF00D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F00ABAB848400FFFF" + $"FFFF00D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFF00D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D8D8D8D8D83F3F003F3F3F00ABAB8400FFFFFF" + $"FFFF0000000000000000000084D8D8D8D8D8D8D8003F3F3F00ABA400FFFFFFFF" + $"FF002A2A2A2A2A2A2A2A2A2A00D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FF002A2A2A5A3F3F5A2ABC2A00D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FF002A2A5A3F9BBC3F3F2A2A00D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FF002A2A9CBC3F3F5ADABC2A00D8D8D8D8D8D8D8D8D8003F00ABA400FFFFFFFF" + $"FF002A2A5A3F7A2A3F5ABC2A00D8D8D8D8D8D8D8D8D8003F00AB00FFFFFFFFFF" + $"FF002A2ABCBC3F3F5A3F2A2A008484D8D8D8D8D8D8D8003F00AB00FFFFFFFFFF" + $"FF002ABCBCBC2A2B3F9B2A2A0000008484D8D8D8D8D8D80000AB00FFFFFFFFFF" + $"FF002ABCBC2A2ADA5A2ABC2A00FFFF00008484D8D8D8D80000AB00FFFFFFFFFF" + $"FF002ABCBC5A1E5ABC2ABC2A00FFFFFFFF00008484D8D80000AB000D0DFFFFFF" + $"FF002A2A2A2A2A2A2A2A2A2A00FFFFFFFFFFFF00008484840000000D0D0D0DFF" + $"FFFF00000000000000000000FFFFFFFFFFFFFFFFFF00008400000D0D0D0D0DFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000D0D0DFFFFFFFF" +}; + +resource(R_BootVolumeIcon) #'ICON' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFF00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFF001A0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFF001A1A1A1A0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF001A1A1A1A1A1A1A0000FFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFF001A1A1A1A1A1A1A1A1A1A0000FFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFF001A1A1A1A1A1A1A1A1A1A1A1A1A0000FFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFF001A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A0000FFFFFFFFFFFFFF" + $"FFFFFFFFFF001A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A0000FFFFFFFFFF" + $"FFFFFFFF001A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A0000FFFFFF" + $"FFFFFF001A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A3F150F00FFFF" + $"FFFF001A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A3F150F0F00FFFF" + $"FF001A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A3F150F0F0F00FFFF" + $"001A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A3F150F0F0F0F00FFFF" + $"003F3F1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A3F150F0F0F0F0F00FFFF" + $"0015153F3F1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A1A3F150F0F0F0F0F0F000F0F" + $"00151515153F3F1A1A1A1A1A1A1A1A1A1A1A1A1A3F150F0F0F0F0F0F000F0F0F" + $"001515151515153F3F1A1A1A1A1A1A1A1A1A1A3F150F0F0F0F0F0F000F0F0FFF" + $"0015000000000000000000001A1A1A1A1A1A3F150F0F0F0F0F0F000F0F0FFFFF" + $"00009C9C9C9C9C9C9C9C9C9C001A1A1A1A3F0F0F0F0F0F0F0F000F0F0FFFFFFF" + $"00009C9C9C5A3F3F5A9C9C9C003F3F1A3F0F0F0F0F0F0F0F000F0F0FFFFFFFFF" + $"FF009C9C5A3F7B9B3F3F9C9C0015153F150F0F0F0F0F0F000F0F0FFFFFFFFFFF" + $"FF009C9C7B9C3F3F5ADA9C9C0015151A150F0F0F0F0F000F0F0FFFFFFFFFFFFF" + $"FF009C9C5A3F5A9C3F5A9C9C0015151A150F0F0F0F000F0F0FFFFFFFFFFFFFFF" + $"FF009C9C9C9C3F3F5A3F9C9C0015151A150F0F0F000F0F0FFFFFFFFFFFFFFFFF" + $"FF009C9C9C9C9C9C3F7B9C9C0015151A150F0F000F0F0FFFFFFFFFFFFFFFFFFF" + $"FF009C9C9C9C9CDA5A9C9C9C0015151A150F000F0F0FFFFFFFFFFFFFFFFFFFFF" + $"FF009C9C9B5A1E5A9B9C9C9C0000001A15000F0F0FFFFFFFFFFFFFFFFFFFFFFF" + $"FF009C9C9C9C9C9C9C9C9C9C00FFFF00000F0F0FFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFF00000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" +}; + +resource(R_FontDirIcon) #'ICON' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF00D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF0000FFFF0084D8D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF003F000000848484D8D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF003F3F3F0000ABA48484D8D80000FFFFFF000000FFFFFFFFFFFFFF" + $"FFFFFFFFFF003F3F3F3F3F0000ABA48484D8D8000000D8D8D80000FFFFFFFFFF" + $"FFFFFFFFFF003F3F3F3F3F3F3F0000ABA48484D8D8D8848484D8D80000FFFFFF" + $"FFFFFFFFFF003F3F3F3F3F3F3F3F3F0000ABA48484848484848484D8D800FFFF" + $"FF000000FF003F3F3F3F3F3F3F3F3F3F3F0000ABA4848484848484848400FFFF" + $"FF003F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000ABA484848484848400FFFF" + $"FF00D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000ABA48484848400FFFF" + $"FF00D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000AB84848400FFFF" + $"FFFF00D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F00ABAB848400FFFF" + $"FFFF00D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFF00D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D8D8D8D8D83F3F003F3F3F00ABAB8400FFFFFF" + $"FFFFFFFF00D8000000D8D8D8D8D8D8D8D8D8D8D8003F3F3F00ABA400FFFFFFFF" + $"FFFFFFFF00008C8C8C00D8D8D8D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FFFFFFFF00F2F2F28CED00D8D8D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FFFFFFFF00F2F2F2F2ED00D8D8D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FFFFFF00F2F200F2F2ED00D8D8D8D8D8D8D8D8D8D8D8003F00ABA400FFFFFFFF" + $"FFFFFF00F2F200EDF2F2ED0084D8D8D8D8D8D8D8D8D8003F00AB00FFFFFFFFFF" + $"FFFF00F2F2F2F2EDF2F2ED00008484D8D8D8D8D8D8D8003F00AB00FFFFFFFFFF" + $"FFFF00F2F2EDF2F2F2F2ED00FF00008484D8D8D8D8D8D80000AB00FFFFFFFFFF" + $"FFFF00F2EDED0000F2F2F2ED00FFFF00008484D8D8D8D80000AB00FFFFFFFFFF" + $"FFFFFF000000FFFF00F2F2ED00FFFFFFFF00008484D8D80000AB000D0DFFFFFF" + $"FFFFFFFFFFFFFFFF00F2F2ED00FFFFFFFFFFFF00008484840000000D0D0D0DFF" + $"FFFFFFFFFFFFFFFFFF000000FFFFFFFFFFFFFFFFFF00008400000D0D0D0D0DFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000D0D0DFFFFFFFF" +}; + +resource(R_AppsDirIcon) #'ICON' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF00D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF0000FFFF0084D8D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF003F000000848484D8D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF003F3F3F0000ABA48484D8D80000FFFFFF000000FFFFFFFFFFFFFF" + $"FFFFFFFFFF003F3F3F3F3F0000ABA48484D8D8000000D8D8D80000FFFFFFFFFF" + $"FFFFFFFFFF003F3F3F3F3F3F3F0000ABA48484D8D8D8848484D8D80000FFFFFF" + $"FFFFFFFFFF003F3F3F3F3F3F3F3F3F0000ABA48484848484848484D8D800FFFF" + $"FF000000FF003F3F3F3F3F3F3F3F3F3F3F0000ABA4848484848484848400FFFF" + $"FF003F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000ABA484848484848400FFFF" + $"FF00D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000ABA48484848400FFFF" + $"FF00D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000AB84848400FFFF" + $"FFFF00D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F00ABAB848400FFFF" + $"FFFF00D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFF00D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D8D8D8D8D83F3F003F3F3F00ABAB8400FFFFFF" + $"FFFFFFFF00D8000000D8D8D8D8D8D8D8D8D8D8D8003F3F3F00ABA400FFFFFFFF" + $"FFFFFFFF00004545450000D8D8D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FFFFFFFF00F94545455D00D8D8D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FFFFFFFF00F9F9F95D5D00D8D8D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FFFFFF0000F9F9F95D5D00D8D8D8D8D8D8D8D8D8D8D8003F00ABA400FFFFFFFF" + $"FFFF00800000F9F95D002D0000D8D8D8D8D8D8D8D8D8003F00AB00FFFFFFFFFF" + $"FF00808080800000007A7A2D008484D8D8D8D8D8D8D8003F00AB00FFFFFFFFFF" + $"FF00A6A68005002DC4C42D2D0000008484D8D8D8D8D8D80000AB00FFFFFFFFFF" + $"FF00A6A60505002DC4C42D2D00FFFF00008484D8D8D8D80000AB00FFFFFFFFFF" + $"FF00A6A60505002DC4C42D2D00FFFFFFFF00008484D8D80000AB000D0DFFFFFF" + $"FF0000A60500000000C42D0000FFFFFFFFFFFF00008484840000000D0D0D0DFF" + $"FFFFFF000000FFFFFF000000FFFFFFFFFFFFFFFFFF00008400000D0D0D0D0DFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000D0D0DFFFFFFFF" +}; + +resource(R_PrefsDirIcon) #'ICON' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF00D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF0000FFFF0084D8D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF003F000000848484D8D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF003F3F3F0000ABA48484D8D80000FFFFFF000000FFFFFFFFFFFFFF" + $"FFFFFFFFFF003F3F3F3F3F0000ABA48484D8D8000000D8D8D80000FFFFFFFFFF" + $"FFFFFFFFFF003F3F3F3F3F3F3F0000ABA48484D8D8D8848484D8D80000FFFFFF" + $"FFFFFFFFFF003F3F3F3F3F3F3F3F3F0000ABA48484848484848484D8D800FFFF" + $"FF000000FF003F3F3F3F3F3F3F3F3F3F3F0000ABA4848484848484848400FFFF" + $"FF003F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000ABA484848484848400FFFF" + $"FF00D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000ABA48484848400FFFF" + $"FF00D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000AB84848400FFFF" + $"FFFF00D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F00ABAB848400FFFF" + $"FFFF00D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFF00D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D8D8D8D8D83F3F003F3F3F00ABAB8400FFFFFF" + $"FFFFFFFF00D8D8D8000000D8D8D8D8D8D8D8D8D8003F3F3F00ABA400FFFFFFFF" + $"FFFFFFFF00D8D8002CA3A300D8D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FFFFFFFF008484002C2C3200D8D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FFFFFFFFFF00002C2CEB00D8D8D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FFFF000000FF002C2C3200D8D8D8D8D8D8D8D8D8D8D8003F00ABA400FFFFFFFF" + $"FF002F7B7B002C2C2F00008484D8D8D8D8D8D8D8D8D8003F00AB00FFFFFFFFFF" + $"FF002F2B9C002C2C3100FF00008484D8D8D8D8D8D8D8003F00AB00FFFFFFFFFF" + $"FFFF002B2B2B2B2F00FFFFFFFF00008484D8D8D8D8D8D80000AB00FFFFFFFFFF" + $"FFFF00302B2B2C3200FFFFFFFFFFFF00008484D8D8D8D80000AB00FFFFFFFFFF" + $"FFFFFF002C2CEB00FFFFFFFFFFFFFFFFFF00008484D8D80000AB000D0DFFFFFF" + $"FFFFFF00F1F13100FFFFFFFFFFFFFFFFFFFFFF00008484840000000D0D0D0DFF" + $"FFFFFFFF000000FFFFFFFFFFFFFFFFFFFFFFFFFFFF00008400000D0D0D0D0DFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000D0D0DFFFFFFFF" +}; + +resource(R_MailDirIcon) #'ICON' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF00D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF0000FFFF0084D8D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF003F000000848484D8D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF003F3F3F0000ABA48484D8D80000FFFFFF000000FFFFFFFFFFFFFF" + $"FFFFFFFFFF003F3F3F3F3F0000ABA48484D8D8000000D8D8D80000FFFFFFFFFF" + $"FFFFFFFFFF003F3F3F3F3F3F3F0000ABA48484D8D8D8848484D8D80000FFFFFF" + $"FFFFFFFFFF003F3F3F3F3F3F3F3F3F0000ABA48484848484848484D8D800FFFF" + $"FF000000FF003F3F3F3F3F3F3F3F3F3F3F0000ABA4848484848484848400FFFF" + $"FF003F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000ABA484848484848400FFFF" + $"FF00D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000ABA48484848400FFFF" + $"FF00D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000AB84848400FFFF" + $"FFFF00D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F00ABAB848400FFFF" + $"FFFF00D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFF00D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D8D8D8D8D83F3F003F3F3F00ABAB8400FFFFFF" + $"FFFFFFFF00D8D8D8D8D8D8D8D8D8D8D8D8D8D8D8003F3F3F00ABA400FFFFFFFF" + $"FFFFFFFF00D8D8D8D8D8D8D8D8D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FF000000000000000000000000D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FF000F1515FCFCFCFCFC2A2A00D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FF001818FCFCFCFCFCFC2A2A00D8D8D8D8D8D8D8D8D8003F00ABA400FFFFFFFF" + $"FF003FFCFC15891562FCFCF900D8D8D8D8D8D8D8D8D8003F00AB00FFFFFFFFFF" + $"FF003FFCFC89158915FCFCF9008484D8D8D8D8D8D8D8003F00AB00FFFFFFFFFF" + $"FF003FFCFC186262FCFCFCF90000008484D8D8D8D8D8D80000AB00FFFFFFFFFF" + $"FF003FFCFCFCFCFCFCFCFCF900FFFF00008484D8D8D8D80000AB00FFFFFFFFFF" + $"FF000000000000000000000000FFFFFFFF00008484D8D80000AB000D0DFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00008484840000000D0D0D0DFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00008400000D0D0D0D0DFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000D0D0DFFFFFFFF" +}; + +resource(R_QueryDirIcon) #'ICON' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF00D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF0000FFFF0084D8D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF003F000000848484D8D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF003F3F3F0000ABA48484D8D80000FFFFFF000000FFFFFFFFFFFFFF" + $"FFFFFFFFFF003F3F3F3F3F0000ABA48484D8D8000000D8D8D80000FFFFFFFFFF" + $"FFFFFFFFFF003F3F3F3F3F3F3F0000ABA48484D8D8D8848484D8D80000FFFFFF" + $"FFFFFFFFFF003F3F3F3F3F3F3F3F3F0000ABA48484848484848484D8D800FFFF" + $"FF000000FF003F3F3F3F3F3F3F3F3F3F3F0000ABA4848484848484848400FFFF" + $"FF003F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000ABA484848484848400FFFF" + $"FF00D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000ABA48484848400FFFF" + $"FF00D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000AB84848400FFFF" + $"FFFF00D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F00ABAB848400FFFF" + $"FFFF00D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFF00D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D8D8D8D8D83F3F003F3F3F00ABAB8400FFFFFF" + $"FFFFFFFF00D8000000D8D8D8D8D8D8D8D8D8D8D8003F3F3F00ABA400FFFFFFFF" + $"FFFFFFFF0000121B1B0000D8D8D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FFFFFFFF003F1B12121B1B0000D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FFFFFF001B12121B1B123FD100D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FFFF0012123F1B12123F03D800D8D8D8D8D8D8D8D8D8003F00ABA400FFFFFFFF" + $"FF00033F3F12123F3F03D8AB00D8D8D8D8D8D8D8D8D8003F00AB00FFFFFFFFFF" + $"FF00D803033F3F3F03D8AB00008484D8D8D8D8D8D8D8003F00AB00FFFFFFFFFF" + $"FF0084D8D8030303D8AB00FFFF00008484D8D8D8D8D8D80000AB00FFFFFFFFFF" + $"FF0084D1ABD8D8D8AB00FFFFFFFFFF00008484D8D8D8D80000AB00FFFFFFFFFF" + $"FF000084AB8484AB00FFFFFFFFFFFFFFFF00008484D8D80000AB000D0DFFFFFF" + $"FFFFFF000084AB00FFFFFFFFFFFFFFFFFFFFFF00008484840000000D0D0D0DFF" + $"FFFFFFFFFF0000FFFFFFFFFFFFFFFFFFFFFFFFFFFF00008400000D0D0D0D0DFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000D0D0DFFFFFFFF" +}; + +resource(R_SpoolFileIcon) #'ICON' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000FFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFF00002B2B2B2B2B0000FFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFF002B2B2B2B2B2B2B2B2B00FFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFF002B2B2B2B0000002B2B2B2B00FFFFFFFFFFFFFFFF" + $"FFFFFF00000000FFFFFFFF002B2B2B2B0000002B2B2B2B00FFFFFFFFFFFFFFFF" + $"FF00001717171700FFFFFF00002B2B2B2B2B2B2B2B2B0000FFFFFFFFFFFFFFFF" + $"FF1717FFFFFFFF1700FFFF002C00002B2B2B2B2B0000EB00FFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFF1700FFFF00002A2A0000000000EBEB00FFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFF1700FF00600000002C2C2CEBEB000000FFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFF1700603F0017170000000000171700FFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFF17003F60003F3F17171717163F3F00FFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFF0017003F3F0016171E3F3F3F3F17160000FFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFF006017003F60003F3F17171616173F3F003F0000FFFFFFFFFFFF" + $"FFFFFFFFFF00603F3F1700000016173F3F3F3F1E1717003F3F3F0000FFFFFFFF" + $"FFFFFFFF00603F3F3F3F1717003F3F16171617173F3F003F3F3F3F3F0000FFFF" + $"FFFFFF00603F3F3F3F3F3F3F0017163F3F3F3F3F1716003F3F3F3F3F603F00FF" + $"FFFF00603F603F603F603F00003F3F16161716163F3F00003F603F603F00FFFF" + $"FF00603F603F603F603F60002B16173F3F3F3F3F16172B00603F603F00AEAEAE" + $"00003F603F603F603F603F00002B2B17161716172B2B00003F603F0000AEAEFF" + $"FFAE0000603F603F603F60002C00002B2B2B2B2B0000EB00603F00AE8700AEFF" + $"FFFFAE00000060606060603F002A2A0000000000EBEB00606000AE87870100AE" + $"FFFFFFAE00AE0000606060603F00002C2C2CEBEB0000606000AE870000AEAEAE" + $"FFFFFFFFAE00AEAE00006087608760000000000060876029AE0000AEAEFFFFFF" + $"FFFFFFFFFFAE0087AEAE000087878787878787878787010000AEAEFFFFFFFFFF" + $"FFFFFFFFFFFFAE008787AEAE0000878787878787870100AEAEFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFAE00608787AEAE0000AEAEAEAE00AEAEFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFAE0060608787AEAE0000AE00AEAEFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFAE00606060870000AE00AEAEFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFAE00600000AEAEAEAEAEFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFAE00AEAEFFFFFFAEFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFAEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" +}; + +resource(R_GenericPrinterIcon) #'ICON' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFF0000FFFF0404FFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFF003F3F00043F3F0404FFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFF003F1717113F3FFE3F3F0404FFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFF003F1717113FFEFEFEFEFE3F3F0404FFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF003F1717113FFDFEFEFEFEFEFEFE3F3F0404FFFFFFFFFF" + $"FFFFFFFFFFFFFFFF003F1717113FFDFDFDFDFEFEFEFEFEFEFEFEFE04FFFFFFFF" + $"FFFFFFFFFFFFFF003F1717113F62FDFDFDFDFDFDFEFEFEFEFEFEFE04FFFFFFFF" + $"FFFFFFFFFFFF003F1717113F626262FDFDFDFDFDFDFDFEFEFEFE0000FFFFFFFF" + $"FFFFFFFFFF003F1717113F626262626262FDFDFDFDFDFDFDFE0B11170000FFFF" + $"FFFFFFFF003F1717113F898962626262626262FDFDFDFDFD0B1117171700FFFF" + $"FFFFFF003F17171711118989898962626262626262FDFD0B111717170B00FFFF" + $"FFFF003F17173F1717171111898989896262626262620B111717170B0B00FFFF" + $"FF003F171717113F3F1717171111898989896262623F171717170B0B0B00FFFF" + $"003F171717171711113F3F1717171111898989893F171717170B0B0B0B000FFF" + $"003F3F17171717171711113F3F1717171111893F171717170B0B0B0B0B000F0F" + $"0011113F3F17171717171711113F3F17171711171717170B0B0B0B0B0B000F0F" + $"00111111113F3F17171717171711113F3F17171717170B0B0B0B0B0B0B000F0F" + $"001111111111113F3F17171717171711113F1717170B0B0B0B0B0B0B000F0F0F" + $"0011111111111111113F3F1717171717171117170B0B0B0B0B0B0B000F0F0FFF" + $"001111113F1111111111113F3F1717171717170B0B0B0B0B0B0B000F0F0FFFFF" + $"0011113F1C1C1C1111111111113F3F1717170B0B0B0B0B0B0B000F0F0FFFFFFF" + $"00113F3F1C1C1C1C1C1111111111113F170B0B0B0B0B0B0B000F0F0FFFFFFFFF" + $"00113F113F3F1C1C1C1C1C11111111110B0B0B0B0B0B0B000F0F0FFFFFFFFFFF" + $"00003F1111113F3F1C1C1C1C1C1111110B0B0B0B0B0B000F0F0FFFFFFFFFFFFF" + $"FFFF0000111111113F3F1C1C1C0511110B0B0B0B0B000F0F0FFFFFFFFFFFFFFF" + $"FFFF0B0B0000111111113F3F050511110B0B0B0B000F0F0FFFFFFFFFFFFFFFFF" + $"FFFFFFFF0B0B000011111105050511110B0B0B000F0F0FFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFF0B0B00001105001111110B0B000F0F0FFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFF0F0F00000F0000110B000F0F0FFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFF0F0F0FFFFF00000F0F0FFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" +}; + +resource(R_DevelopDirIcon) #'ICON' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF00D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF0000FFFF0084D8D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF003F000000848484D8D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF003F3F3F0000ABA48484D8D80000FFFFFF000000FFFFFFFFFFFFFF" + $"FFFFFFFFFF003F3F3F3F3F0000ABA48484D8D8000000D8D8D80000FFFFFFFFFF" + $"FFFFFFFFFF003F3F3F3F3F3F3F0000ABA48484D8D8D8848484D8D80000FFFFFF" + $"FFFFFFFFFF003F3F3F3F3F3F3F3F3F0000ABA48484848484848484D8D800FFFF" + $"FF000000FF003F3F3F3F3F3F3F3F3F3F3F0000ABA4848484848484848400FFFF" + $"FF003F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000ABA484848484848400FFFF" + $"FF00D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000ABA48484848400FFFF" + $"FF00D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000AB84848400FFFF" + $"FFFF00D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F00ABAB848400FFFF" + $"FFFF00D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFF00D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D8D8D8D8D83F3F003F3F3F00ABAB8400FFFFFF" + $"FFFFFFFF00D8D8D8D8D8D8D8D8D8D8D8D8D8D8D8003F3F3F00ABA400FFFFFFFF" + $"FFFFFFFF00000000D8D8D8D8D8D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FFFFFFFF003F003F00D8D8D8D8D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FFFFFFFF0000000000D8D8D8D8D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FF00FF000000000000008400D8D8D8D8D8D8D8D8D8D8003F00ABA400FFFFFFFF" + $"FFFF00005A5A002AEB00008484D8D8D8D8D8D8D8D8D8003F00AB00FFFFFFFFFF" + $"FFFF005A3F2A002A2AEB0000008484D8D8D8D8D8D8D8003F00AB00FFFFFFFFFF" + $"FF00005A002A002A2AEB0000FF00008484D8D8D8D8D8D80000AB00FFFFFFFFFF" + $"FFFF005AA32A002A00EB00FFFFFFFF00008484D8D8D8D80000AB00FFFFFFFFFF" + $"FFFF005AA32A002A2AEB00FFFFFFFFFFFF00008484D8D80000AB000D0DFFFFFF" + $"FF00FF00A32A002AEB00FF00FFFFFFFFFFFFFF00008484840000000D0D0D0DFF" + $"FFFFFFFF0000000000FFFFFFFFFFFFFFFFFFFFFFFF00008400000D0D0D0D0DFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000D0D0DFFFFFFFF" +}; + +resource(R_DownloadDirIcon) #'ICON' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF00D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF0000FFFF0084D8D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF003F000000848484D8D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF003F3F3F0000ABA48484D8D80000FFFFFF000000FFFFFFFFFFFFFF" + $"FFFFFFFFFF003F3F3F3F3F0000ABA48484D8D8000000D8D8D80000FFFFFFFFFF" + $"FFFFFFFFFF003F3F3F3F3F3F3F0000ABA48484D8D8D8848484D8D80000FFFFFF" + $"FFFFFFFFFF003F3F3F3F3F3F3F3F3F0000ABA48484848484848484D8D800FFFF" + $"FF000000FF003F3F3F3F3F3F3F3F3F3F3F0000ABA4848484848484848400FFFF" + $"FF003F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000ABA484848484848400FFFF" + $"FF00D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000ABA48484848400FFFF" + $"FF00D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000AB84848400FFFF" + $"FFFF00D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F00ABAB848400FFFF" + $"FFFF00D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFF00D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D8D8D8D8D83F3F003F3F3F00ABAB8400FFFFFF" + $"FFFFFFFF00D8D8D8D8D80000D8D8D8D8D8D8D8D8003F3F3F00ABA400FFFFFFFF" + $"FF000000000000000000563B00D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FF00F21D3F3F00560056373700D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FF00F21D3F3F005657573500D8D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FF00F21D3F1D005657570000D8D8D8D8D8D8D8D8D8D8003F00ABA400FFFFFFFF" + $"FF00F2861D1B00565757570084D8D8D8D8D8D8D8D8D8003F00AB00FFFFFFFFFF" + $"FF00F2F3F3F3000000000000008484D8D8D8D8D8D8D8003F00AB00FFFFFFFFFF" + $"FF00F2101414141410F2F200FF00008484D8D8D8D8D8D80000AB00FFFFFFFFFF" + $"FF00F3142424141414F3F200FFFFFF00008484D8D8D8D80000AB00FFFFFFFFFF" + $"FF00F3132424141414F3F200FFFFFFFFFF00008484D8D80000AB000D0DFFFFFF" + $"FF00F3142424141414F3F200FFFFFFFFFFFFFF00008484840000000D0D0D0DFF" + $"FFFF00000000000000000000FFFFFFFFFFFFFFFFFF00008400000D0D0D0D0DFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000D0D0DFFFFFFFF" +}; + +resource(R_PersonDirIcon) #'ICON' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF00D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF0000FFFF0084D8D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF003F000000848484D8D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF003F3F3F0000ABA48484D8D80000FFFFFF000000FFFFFFFFFFFFFF" + $"FFFFFFFFFF003F3F3F3F3F0000ABA48484D8D8000000D8D8D80000FFFFFFFFFF" + $"FFFFFFFFFF003F3F3F3F3F3F3F0000ABA48484D8D8D8848484D8D80000FFFFFF" + $"FFFFFFFFFF003F3F3F3F3F3F3F3F3F0000ABA48484848484848484D8D800FFFF" + $"FF000000FF003F3F3F3F3F3F3F3F3F3F3F0000ABA4848484848484848400FFFF" + $"FF003F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000ABA484848484848400FFFF" + $"FF00D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000ABA48484848400FFFF" + $"FF00D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000AB84848400FFFF" + $"FFFF00D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F00ABAB848400FFFF" + $"FFFF00D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFF00D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D8D8D8D8D83F3F003F3F3F00ABAB8400FFFFFF" + $"FFFFFFFF00D8D80000D8D8D8D8D8D8D8D8D8D8D8003F3F3F00ABA400FFFFFFFF" + $"FFFFFFFF00D8003F3F00D8D8D8D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FFFFFFFF0084000E0E00D8D8D8D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FFFFFFFFFF00FA00000000D8D8D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FFFFFFFFFF00F9F9FAFAFA00D8D8D8D8D8D8D8D8D8D8003F00ABA400FFFFFFFF" + $"FFFFFFFFFF00F9F9F9F97D0084D8D8D8D8D8D8D8D8D8003F00AB00FFFFFFFFFF" + $"FFFFFFFFFFFF00F9F9F97D00008484D8D8D8D8D8D8D8003F00AB00FFFFFFFFFF" + $"FFFFFFFFFFFF00F9F97D00FFFF00008484D8D8D8D8D8D80000AB00FFFFFFFFFF" + $"FFFFFFFFFFFF00F9F97D00FFFFFFFF00008484D8D8D8D80000AB00FFFFFFFFFF" + $"FFFFFFFFFFFF00F9F97D00FFFFFFFFFFFF00008484D8D80000AB000D0DFFFFFF" + $"FFFFFFFFFFFF0000F97D00FFFFFFFFFFFFFFFF00008484840000000D0D0D0DFF" + $"FFFFFFFFFFFFFFFF0000FFFFFFFFFFFFFFFFFFFFFF00008400000D0D0D0D0DFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000D0D0DFFFFFFFF" +}; + +resource(R_UtilDirIcon) #'ICON' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF00D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF0000FFFF0084D8D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF003F000000848484D8D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF003F3F3F0000ABA48484D8D80000FFFFFF000000FFFFFFFFFFFFFF" + $"FFFFFFFFFF003F3F3F3F3F0000ABA48484D8D8000000D8D8D80000FFFFFFFFFF" + $"FFFFFFFFFF003F3F3F3F3F3F3F0000ABA48484D8D8D8848484D8D80000FFFFFF" + $"FFFFFFFFFF003F3F3F3F3F3F3F3F3F0000ABA48484848484848484D8D800FFFF" + $"FF000000FF003F3F3F3F3F3F3F3F3F3F3F0000ABA4848484848484848400FFFF" + $"FF003F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000ABA484848484848400FFFF" + $"FF00D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000ABA48484848400FFFF" + $"FF00D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000AB84848400FFFF" + $"FFFF00D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F00ABAB848400FFFF" + $"FFFF00D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFF00D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D8D8D8D8D83F3F003F3F3F00ABAB8400FFFFFF" + $"FFFFFFFF00D8D8D8000000D8D8D8D8D8D8D8D8D8003F3F3F00ABA400FFFFFFFF" + $"FFFFFFFF00D8D8003F1A00D8D8D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FFFFFFFF008484003F00D80000D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FFFFFFFFFF0000003F18000F00D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FFFFFFFFFFFF00181818181800D8D8D8D8D8D8D8D8D8003F00ABA400FFFFFFFF" + $"FFFFFFFFFF0018181800000084D8D8D8D8D8D8D8D8D8003F00AB00FFFFFFFFFF" + $"FFFF00000018000000000000008484D8D8D8D8D8D8D8003F00AB00FFFFFFFFFF" + $"FF003F181818003F0F0F0F0F0000008484D8D8D8D8D8D80000AB00FFFFFFFFFF" + $"FF003F001818000F09094A0900FFFF00008484D8D8D8D80000AB00FFFFFFFFFF" + $"FF00003F0018004A094A094A00FFFFFFFF00008484D8D80000AB000D0DFFFFFF" + $"FFFFFF000F1800094A09090900FFFFFFFFFFFF00008484840000000D0D0D0DFF" + $"FFFFFF00000000000000000000FFFFFFFFFFFFFFFF00008400000D0D0D0D0DFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000D0D0DFFFFFFFF" +}; + +resource(R_ConfigDirIcon) #'ICON' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFF00D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF0000FFFF0084D8D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF003F000000848484D8D80000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF003F3F3F0000ABA48484D8D80000FFFFFF000000FFFFFFFFFFFFFF" + $"FFFFFFFFFF003F3F3F3F3F0000ABA48484D8D8000000D8D8D80000FFFFFFFFFF" + $"FFFFFFFFFF003F3F3F3F3F3F3F0000ABA48484D8D8D8848484D8D80000FFFFFF" + $"FFFFFFFFFF003F3F3F3F3F3F3F3F3F0000ABA48484848484848484D8D800FFFF" + $"FF000000FF003F3F3F3F3F3F3F3F3F3F3F0000ABA4848484848484848400FFFF" + $"FF003F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000ABA484848484848400FFFF" + $"FF00D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000ABA48484848400FFFF" + $"FF00D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F3F0000AB84848400FFFF" + $"FFFF00D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F3F3F00ABAB848400FFFF" + $"FFFF00D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFF00D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D8D8D83F3F00003F3F3F3F00ABAB8400FFFFFF" + $"FFFFFF00D8D8D8D8D8D8D8D8D8D8D8D8D8D83F3F003F3F3F00ABAB8400FFFFFF" + $"FFFFFFFF00D8D8D8D8D8D8D8D8D8D8D8D8D8D8D8003F3F3F00ABA400FFFFFFFF" + $"FFFF00000000000000000000D8D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FFFF005A2A004F36003FFA00D8D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FFFF002A2A00363600FAFA00D8D8D8D8D8D8D8D8D8003F3F00ABA400FFFFFFFF" + $"FFFF00000000000000000000D8D8D8D8D8D8D8D8D8D8003F00ABA400FFFFFFFF" + $"FFFF00D898003F3F004F360084D8D8D8D8D8D8D8D8D8003F00AB00FFFFFFFFFF" + $"FFFF009898003F3F00363600008484D8D8D8D8D8D8D8003F00AB00FFFFFFFFFF" + $"FFFF00000000000000000000FF00008484D8D8D8D8D8D80000AB00FFFFFFFFFF" + $"FFFF004F36007220005A2A00FFFFFF00008484D8D8D8D80000AB00FFFFFFFFFF" + $"FFFF003636002020002A2A00FFFFFFFFFF00008484D8D80000AB000D0DFFFFFF" + $"FFFF00000000000000000000FFFFFFFFFFFFFF00008484840000000D0D0D0DFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00008400000D0D0D0D0DFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000D0D0DFFFFFFFF" +}; + +resource(1, "BEOS:M:application/x-vnd.Be-directory") #'MICN' array { + $"FFFFFFFFFF0000FFFFFFFFFFFFFFFFFF" + $"FFFFFFFF0084840000FFFF0000FFFFFF" + $"FFFFFF000000AA84840000D8D80000FF" + $"FFFFFF003F3F0000AA84848484D8D800" + $"0000FF003F3F3F1F0000AA8484848400" + $"003F00003F3F3F3F3F1F0000AA848400" + $"00D83F3F00003F3F3F3F3F3F00AA8400" + $"FF00D8D83F3F00003F3F3F3F00AA8400" + $"FF00D8D8D8D83F3F00003F3F00AA8400" + $"FFFF00D8D8D8D8D83F3F003F00AA00FF" + $"FFFF008484D8D8D8D8D8003F00AA00FF" + $"FFFFFF00008484D8D8D8D80000AA00FF" + $"FFFFFFFFFF00008484D8D80000AA00FF" + $"FFFFFFFFFFFFFF000084848400AA0010" + $"FFFFFFFFFFFFFFFFFF00008400000010" + $"FFFFFFFFFFFFFFFFFFFFFF00000010FF" +}; + +resource(2, "BEOS:M:application/x-vnd.Be-query") #'MICN' array { + $"FFFFFFFFFFFFFF0000FFFFFFFFFFFFFF" + $"FFFFFFFFFFFF003F3F0000FFFFFFFFFF" + $"FFFFFFFFFF00001B1A3F3F0000FFFFFF" + $"FFFFFFFF003F3F00001B1A3F0000FFFF" + $"FFFFFF0000191A3F3F00001900D100FF" + $"FFFF00003F00001B1A1B000F00D900FF" + $"FF00D1001B3F3F000019000FD9AA00FF" + $"00D1D100191A1B3F001500D9AAAA000F" + $"00D9D10015151A19000FD9AAAA000F0F" + $"0083D9D90F0F151600D9AAAA000F0FFF" + $"0083D1D1D9D90F0FD9AAAA000F0EFFFF" + $"0083D1AAD183AAD9AAAA000F0FFFFFFF" + $"0083D9AAAA83AAAAAA000F0FFFFFFFFF" + $"FF0000D9D983AAAA000F0FFFFFFFFFFF" + $"FFFFFF000083AA000F0EFFFFFFFFFFFF" + $"FFFFFFFFFF00000E0FFFFFFFFFFFFFFF" +}; + +resource(3, "BEOS:M:application/x-vnd.Be-volume") #'MICN' array { + $"FFFFFFFF0000FFFFFFFFFFFFFFFFFFFF" + $"FFFFFF00D93F0000FFFFFFFFFFFFFFFF" + $"FFFF00D93FD93FD90000FFFFFFFFFFFF" + $"FF00D93FD93FD93FD9D90000FFFFFFFF" + $"003FD9D9D9D9D9D9D9D9AA01FFFFFFFF" + $"00833F3FD9D9D9D9D9AAAA00FFFFFFFF" + $"008383833F3FD9D9AAAAAA00FFFFFFFF" + $"0083838383833FAAAAAAAA00FFFFFFFF" + $"00838300D18383AAAAAAAA000FFFFFFF" + $"00AAAA83D98383AAAAAAAA000F0F0FFF" + $"008383AAAA8383AAAAAAAA000F0F0F0F" + $"00838300D1AAAAAAAAAA000F0F0F0FFF" + $"00838383D98383AAAA000F0F0F0FFFFF" + $"FF000083838383AA000F0F0F0FFFFFFF" + $"FFFFFF00008383000F0F0E0FFFFFFFFF" + $"FFFFFFFFFF00000E0F0F0FFFFFFFFFFF" +}; + +resource(4, "BEOS:M:application/x-vnd.be-querytemplate") #'MICN' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFF0000FFFFFFFFFFFF" + $"FFFFFFFFFFFFFF00D9D90000FFFFFFFF" + $"FFFFFFFFFFFF00D9D7AAD9D90000FFFF" + $"FFFFFFFFFF00D9D7D7AAAAAAD9D900FF" + $"FFFFFFFF00D9D7D7D7D1AA0000D900FF" + $"FFFFFF00D9D7D7D7D1D1001D00AA00FF" + $"FF0000D9D7D7D7D1D1003F171D00000F" + $"00D9D9D7D7D7D1D1003F171D1A000F0F" + $"0083D9D9D7D1D1D1003F3F1D171A000F" + $"0083D1D1D9D9D1D1D900170000000FFF" + $"0083D1AAD183AAD9AA00001D1D000FFF" + $"0083D9AAAA83AAAA003F3F1D171A00FF" + $"FF0000D9D983AAAA00003F171A1A000F" + $"FFFFFF000083AA000F003F1D00000F0F" + $"FFFFFFFFFF00000E0FFF00000F0FFFFF" +}; + +resource(6, "BEOS:M:application/x-vnd.be-symlink") #'MICN' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFF000000FFFFFF" + $"FFFFFFFFFFFFFFFFFF00A35AA30000FF" + $"FFFFFFFFFFFFFFFF00A35A5A5AA300FF" + $"FFFFFFFFFFFFFFFF000A3F5A1E2D00FF" + $"0000FFFFFFFFFF003FA35A5A2D2D00FF" + $"003F000000FFFF00A33F5A5A2D2D00FF" + $"00863F870400FF00A3A33FA32D2D000B" + $"008686D5D5D50000A3A3A32D2D000B0B" + $"008686D5D5D500FF0000A32D000B0BFF" + $"008686D5D5D500FFFFFF00000B0BFFFF" + $"FF0086D500000B00000000FFFFFFFFFF" + $"FF0000000B0B00FAFAF95D000BFFFFFF" + $"0000FFFF0000B03F0000000000000000" + $"0000FFFF000000000BFFFF003FFAF900" + $"FFFFFFFFFFFFFFFFFFFFFFFF000000FF" +}; + +resource(R_MICN_AppIcon) #'MICN' array { + $"FFFFFFFFFFFFFF0000FFFFFFFFFFFFFF" + $"FFFFFFFFFFFF00FAFA0000FFFFFFFFFF" + $"FFFFFFFFFF00FAFAFAFAFA0000FFFFFF" + $"FFFFFFFF001FFAFAFAFA1F5D00FFFFFF" + $"FFFFFFFF00F91F1FFA1F5D5D00FFFFFF" + $"FFFFFF0000F9F9F91F5D5D5D00FFFFFF" + $"FFFF006001F9F9F9F95D5D5D0000FFFF" + $"FF00606001F9F9F9F95D5D5D00A30000" + $"001F60606001F9F9F95D5D00A31F2D00" + $"00861F1F601F0000F95D00A31F2D2D00" + $"008686861FD527000000A31F2D2D2E00" + $"0086868686D52801CACAA3A32D2D2E00" + $"0086868686D5D500CAA3A3A32D2D2D00" + $"0086868686D5D500A3A3A3A32D2D2E01" + $"FF00008686D5D5010000A3A32D2E0011" + $"FFFFFF000000001111FF000000001111" +}; + +resource(R_MICN_FileIcon) #'MICN' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFF00FFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFF003F0000FFFFFFFFFFFFFF" + $"FFFFFFFF003F6060600000FFFFFFFFFF" + $"FFFFFF003F6060606060600000FFFFFF" + $"FFFF003F3F3F3F3F3F3F3F60600000FF" + $"FF003F603F603F603F603F3F60600000" + $"00606060606060606060601B600000AE" + $"AE000087878787878787878701870000" + $"FFAE000000878787878787008700AEAE" + $"FFFFAE00AE0000AEAEAE000000AEFFFF" + $"FFFFFFAE0087AE00AE00AEAEFFFFFFFF" + $"FFFFFFFFAE00000000AEAEFFFFFFFFFF" + $"FFFFFFFFFFAE00AEFFAEFFFFFFFFFFFF" +}; + +resource(R_MICN_TrashIcon) #'MICN' array { + $"FFFFFFFFFFFF000000FFFFFFFFFFFFFF" + $"FFFFFFFFFF0000080F0000FFFFFFFFFF" + $"FFFFFFFF000000080F11110000FFFFFF" + $"FFFFFF00000000080F111111110000FF" + $"FFFF0000000000080F1111110F0000FF" + $"FF000E0F000000090F11110F0000FFFF" + $"FF0000040F0F00080F110F040000FFFF" + $"FFFF000000040F0F0E0F000000FFFFFF" + $"FFFF00B5000000040F0000B500FFFFFF" + $"FFFFFF00B5B5B5000000B5B500FFFFFF" + $"FFFFFF006E6EB5B5B5B5B5B500FFFFFF" + $"FFFFFF006E6E6E6EB5B5B5000B0A0BFF" + $"FFFFFFFF006E6E41B5B5B5000A0B0BFF" + $"FFFFFFFF006E6E41B5B5000A0B0BFFFF" + $"FFFFFFFFFF000041B5000A0B0BFFFFFF" + $"FFFFFFFFFFFFFF00000A0B0AFFFFFFFF" +}; + +resource(R_MICN_TrashFullIcon) #'MICN' array { + $"FFFFFFFFFFFF000000FF0000FFFFFFFF" + $"FFFFFFFFFF0000000E00FE0000FFFFFF" + $"FFFFFF000000828200FEB0FD00FFFFFF" + $"0000006200A382822D00B0FD000000FF" + $"00FE62B000A3A32D2E00B0B0FC0000FF" + $"FF000E0F0000A32D0000B00F0000FFFF" + $"FF0000040F0F002D00000E050000FFFF" + $"FFFF000000040F0FD70E000000FFFFFF" + $"FFFF00B5000000040F0000B500FFFFFF" + $"FFFFFF00B5B5B5000000B5B500FFFFFF" + $"FFFFFF006E6EB5B5B5B5B5B500FFFFFF" + $"FFFFFF006E6E6E6EB5B5B5000B0A0BFF" + $"FFFFFFFF006E6E41B5B5B5000A0B0BFF" + $"FFFFFFFF006E6E41B5B5000A0B0BFFFF" + $"FFFFFFFFFF000041B5000A0B0BFFFFFF" + $"FFFFFFFFFFFFFF00000A0B0AFFFFFFFF" +}; + +resource(R_MICN_PrinterIcon) #'MICN' array { + $"FFFFFFFFFFFF0000FFFFFFFFFFFFFFFF" + $"FFFFFFFFFF0017170000FFFFFFFFFFFF" + $"FFFFFFFF0017040411180000FFFFFFFF" + $"FFFFFF0017040405041111170000FFFF" + $"FFFF0017040405FEFE04041117180000" + $"FF00111717058962FDFEFE0417040500" + $"00171111111718898962181704040000" + $"00111817111111181717180404000400" + $"0011111118171111111804040004000B" + $"00040411111118171100040005000A0B" + $"001718040411111104000005000A0BFF" + $"0011111717050411040004000B0BFFFF" + $"FF000011111717050404000B0AFFFFFF" + $"FF0A0B000011110404000B0BFFFFFFFF" + $"FFFFFF0A0B000000000A0BFFFFFFFFFF" + $"FFFFFFFFFF0A0BFFFFFFFFFFFFFFFFFF" +}; + +resource(R_MICN_FloppyIcon) #'MICN' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFF0EFFFFFFFFFFFFFF" + $"FFFFFFFFFF0000000E0F0FFFFFFFFFFF" + $"FFFFFFFF000A002D00000F0F0FFFFFFF" + $"FFFFFF000A003F2D2E2D00000E0F0FFF" + $"FFFF000A003F3F3F3FD2D2D200000E0F" + $"FF000A0B0A00003F3F3F3F000A000F0F" + $"000B0A00000B0A00003F000A04000F0F" + $"003F00171700000B0B000A04000F0FFF" + $"0015003F3F1717000B0B04000F0FFFFF" + $"FF000015153F3F000A04000F0FFFFFFF" + $"FFFFFF000015000A05000F0EFFFFFFFF" + $"FFFFFFFFFF000004000F0FFFFFFFFFFF" + $"FFFFFFFFFFFFFF000E0FFFFFFFFFFFFF" +}; + +resource(R_MICN_CDIcon) #'MICN' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFF00000000000000FFFFFFFFFF" + $"FFFF00001D1E1E1E1E1E1D00000FFFFF" + $"FF001D1E1A1A18181819191E1E000FFF" + $"0060411A3F141D001D143F194160000F" + $"00604144D81E00FF001ED8444160000F" + $"0060414418141E001E141D444160000F" + $"001D1A19193F1818183F1D1A191D000F" + $"FF001D1E3F19191818183F1D1D000F0F" + $"FFFF00001D1E1E1E1E1E1D00000F0FFF" + $"FFFFFFFF000000000000000F0F0FFFFF" + $"FFFFFFFFFFFF0F0F0F0F0F0FFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" +}; + +resource(R_MICN_BeBoxIcon) #'MICN' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFF00FFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFF000A0000FF000000FFFFFFFFFF" + $"FFFF000A002D2E000060600100FFFFFF" + $"FF000A003F3F3FD2D2006060600000FF" + $"00150A0B00003F000400006000AE00FF" + $"0000150A0B0B000400606000AE00FFFF" + $"FF000000150A05001717AEAE000000FF" + $"000E0F0F0000000000AEAE00110F00FF" + $"003F3F0E0F0F0F0F0F0000110F0F000E" + $"0018173F3F0E0F1C1C1B110F0F000F0F" + $"0017352B181F3F1B1C110F0F000E0FFF" + $"000017171817173F0E0F0F000F0FFFFF" + $"FFFF0000171718170F0F000F0FFFFFFF" + $"FFFFFFFF000017170F000F0FFFFFFFFF" + $"FFFFFFFFFFFF0000000E0FFFFFFFFFFF" +}; + +resource(R_MICN_BookmarkIcon) #'MICN' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFF0000FFFFFFFFFFFF" + $"FFFFFFFFFFFF00003F1B0000FFFFFFFF" + $"FFFFFFFFFF001B1C3F3F3F1B0000FFFF" + $"FFFFFFFFFF0000111B1C3F3F3F1B0000" + $"FFFFFF00002D2C0000111B1C3F000E5D" + $"FFFF003F00002D2C2D00001B000F5D00" + $"FFFF003F1B1C00002D2C2D000F5D00FF" + $"FF001B3F3F3F1B1C00002D2C000011FF" + $"FF001B1C3F3F3F3F1B00002C2D2D0000" + $"005D00001C1B3F3F000E5D00002D2C00" + $"00005D1C00001C000F5D00FFFF00003F" + $"FFFF00005D1B000F5D00FFFFFFFFFFFF" + $"FFFFFFFF00005D5D00FFFFFFFFFFFFFF" + $"FFFFFFFFFFFF0000FFFFFFFFFFFFFFFF" +}; + +resource(R_MICN_PersonIcon) #'MICN' array { + $"FFFFFFFFFF020200FFFFFFFFFFFFFFFF" + $"FFFFFFFF04151E1500FFFFFFFFFFFFFF" + $"FFFFFFFF021D1E1C00FFFFFFFFFFFFFF" + $"FFFFFFFF00151B1600FFFFFFFFFFFFFF" + $"FFFFFF00FA0000000000FFFFFFFFFFFF" + $"FFFFFF00F9F9FAFAFAFA00FFFFFFFFFF" + $"FFFFFF00F9F9F9F97D7D00FFFFFFFFFF" + $"FFFFFF00F9F9F9F97D7D00FFFFFFFFFF" + $"FFFFFF00F9F9F9F97D7D00FFFFFFFFFF" + $"FFFFFFFF00F9F9F97D7D00FFFFFFFFFF" + $"FFFFFFFF00F9F97D7D00FFFFFFFFFFFF" + $"FFFFFFFF00F9F97D7D00FFFFFFFFFFFF" + $"FFFFFFFF00F9F97D7D00FFFFFFFFFFFF" + $"FFFFFFFF00F9F97D7D000A0BFFFFFFFF" + $"FFFFFFFF0000F97D7D000A0B0AFFFFFF" + $"FFFFFFFFFFFF0000000A0B0A0BFFFFFF" +}; + +resource(R_MICN_BrokenLinkIcon) #'MICN' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFF000000FFFFFF" + $"FFFFFFFFFFFFFFFFFF00A35AA30000FF" + $"FFFFFFFFFFFFFFFF00A35A5A5AA300FF" + $"FFFFFFFFFFFFFFFF000A3F5A1E2D00FF" + $"0000FFFFFFFFFF003FA35A5A2D2D00FF" + $"003F000000FFFF00A33F5A5A2D2D00FF" + $"00863F870400FF00A3A33FA32D2D000B" + $"008686D5D5D50000A3A3A32D2D000B0B" + $"008686D5D5D500FF0000A32D000B0BFF" + $"008686D5D5D500FFFFFF00000B0BFFFF" + $"FF0086D500000B00000000FFFFFFFFFF" + $"FF0000000B0B00FAFAF95D000BFFFFFF" + $"0000FFFF0000B03F0000000000000000" + $"0000FFFF000000000BFFFF003FFAF900" + $"FFFFFFFFFFFFFFFFFFFFFFFF000000FF" +}; + +resource(R_MICN_DeskIcon) #'MICN' array { + $"FFFFFFFFFF0000FFFF0000FFFFFFFFFF" + $"FFFFFFFF00D9D9000037360000FFFFFF" + $"FFFFFF00D9D9D9D9D909083600FFFFFF" + $"FFFF00D93F3F3FD9D90000000000FFFF" + $"FF00D93F3F15000083150B00AA00FFFF" + $"008383D9D93F83D9D90000D1D100FFFF" + $"00D1D18383D9D9D9D9D9AAD1D100FFFF" + $"0083D1D1D18383D9D9AAD1D1AA00FFFF" + $"008383D1D1D1D183AAD1D1AAAA00FFFF" + $"00AAAAD1D1D1D1D1D1D1AAAAAA00FFFF" + $"008383D1D1D1008383AAAAAAAA000F0F" + $"008383D1D100008383AAAAAAAA000FFF" + $"FF0000D1000E00AAAAAAAAAA000FFFFF" + $"FFFFFF000E0F008383AAAA010EFFFFFF" + $"FFFFFFFFFFFF008383AA000FFFFFFFFF" + $"FFFFFFFFFFFFFF0000000EFFFFFFFFFF" +}; + +resource(R_MICN_HomeDirIcon) #'MICN' array { + $"FFFFFFFFFF0000FFFFFFFFFFFFFFFFFF" + $"FFFFFFFF0084840000FFFF0000FFFFFF" + $"FFFFFF000000AA84840000D8D80000FF" + $"FFFFFF003F1F0000AA84848484D8D800" + $"0000FF003F3F3F1E0000AA8484848400" + $"003F00003F3F3F3F3F1E0000AA848400" + $"00D83F3F00003F3F3F3F3F3F00AA8400" + $"FF00D8D83F3F00003F3F3F3F00AA8400" + $"FF00D80000D83F3F00003F3F00AA8400" + $"FFFF002E2E00D8D83F3F003F00AA00FF" + $"FF002DF7F72E00D8D8D8003F00AA00FF" + $"002EF71212F72E00D8D8D80000AA00FF" + $"FF00123F3F12008484D8D80000AA00FF" + $"FF003F003F3F00000084848400AA000D" + $"FF003F003F3F00FFFF0000840000000D" + $"FF000000000000FFFFFFFF0000000DFF" +}; + +resource(R_MICN_BeosFolderIcon) #'MICN' array { + $"FFFFFFFFFF0000FFFFFFFFFFFFFFFFFF" + $"FFFFFFFF0084840000FFFF0000FFFFFF" + $"FFFFFF000000AA84840000D8D80000FF" + $"FFFFFF003F1F0000AA84848484D8D800" + $"0000FF003F3F3F1E0000AA8484848400" + $"003F00003F3F3F3F3F1E0000AA848400" + $"00D83F3F00003F3F3F3F3F3F00AA8400" + $"FF00D8D83F3F00003F3F3F3F00AA8400" + $"FF0000000000003F00003F3F00AA8400" + $"002A2ADADA2A2A003F3F003F00AA00FF" + $"002A595A593F2A00D8D8003F00AA00FF" + $"002A5ADA7B1E2A00D8D8D80000AA00FF" + $"002A2A7A5A3F2A0084D8D80000AA00FF" + $"00BCBC2A1EBC2A000084848400AA000D" + $"002ABC3F9B2A2A00FF0000840000000D" + $"FF000000000000FFFFFFFF0000000DFF" +}; + +resource(R_MICN_BootVolumeIcon) #'MICN' array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFF0000FFFFFFFFFFFFFFFF" + $"FFFFFFFFFF001A1A0000FFFFFFFFFFFF" + $"FFFFFFFF001A1A1A1A1A0000FFFFFFFF" + $"FFFFFF001A1A1A1A1A1A1A1A0000FFFF" + $"FFFF001A1A1A1A1A1A1A1A1A1A3F00FF" + $"FF001A1A1A1A1A1A1A1A1A1A3F0F00FF" + $"000000000000001A1A1A1A3F0F0F00FF" + $"009C9C1DDA9C9C001A1A3F0F0F0F000F" + $"009CDA59DA3F9C001A3F0E0F0F000F0F" + $"009C59DA7B1E9C003F0E0F0F000F0FFF" + $"009C9C5A5A3F9C000F0F0F000F0FFFFF" + $"009B9B9C1E9B9C000F0F000F0FFFFFFF" + $"009C9B3F7A9C9C000F000F0FFFFFFFFF" + $"FF00000000000000000F0FFFFFFFFFFF" +}; + +resource(R_MICN_FontDirIcon) #'MICN' array { + $"FFFFFFFFFF0000FFFFFFFFFFFFFFFFFF" + $"FFFFFFFF0084840000FFFF0000FFFFFF" + $"FFFFFF000000AA84840000D8D80000FF" + $"FFFFFF003F1F0000AA84848484D8D800" + $"0000FF003F3F3F1E0000AA8484848400" + $"003F00003F3F3F3F3F1E0000AA848400" + $"00D83F3F00003F3F3F3F3F3F00AA8400" + $"FF00D8D83F3F00003F3F3F3F00AA8400" + $"FF00D80000003F3F00003F3F00AA8400" + $"FFFF00F28C00D8D83F3F003F00AA00FF" + $"FFFF00F2F2ED00D8D8D8003F00AA00FF" + $"FF00F200EDED00D8D8D8D80000AA00FF" + $"FF00F2F2F2ED008484D8D80000AA00FF" + $"FF00F200F2F2ED000084848400AA000D" + $"FFFF000000F2ED00FF0000840000000D" + $"FFFFFFFFFF0000FFFFFFFF0000000DFF" +}; + +resource(R_MICN_AppsDirIcon) #'MICN' array { + $"FFFFFFFFFF0000FFFFFFFFFFFFFFFFFF" + $"FFFFFFFF0084840000FFFF0000FFFFFF" + $"FFFFFF000000AA84840000D8D80000FF" + $"FFFFFF003F1F0000AA84848484D8D800" + $"0000FF003F3F3F1E0000AA8484848400" + $"003F00003F3F3F3F3F1E0000AA848400" + $"00D83F3F00003F3F3F3F3F3F00AA8400" + $"FF00D8D83F3F00003F3F3F3F00AA8400" + $"FF00D80000003F3F00003F3F00AA8400" + $"FFFF005D455D00D83F3F003F00AA00FF" + $"FF0000F9F95D00D8D8D8003F00AA00FF" + $"00800000F95D0000D8D8D80000AA00FF" + $"00A6A60500007A0084D8D80000AA00FF" + $"00A6A60500C42D000084848400AA000D" + $"0000A60500C42D00FF0000840000000D" + $"FFFF0000FF0000FFFFFFFF0000000DFF" +}; + +resource(R_MICN_PrefsDirIcon) #'MICN' array { + $"FFFFFFFFFF0000FFFFFFFFFFFFFFFFFF" + $"FFFFFFFF0084840000FFFF0000FFFFFF" + $"FFFFFF000000AA84840000D8D80000FF" + $"FFFFFF003F1F0000AA84848484D8D800" + $"0000FF003F3F3F1E0000AA8484848400" + $"003F00003F3F3F3F3F1E0000AA848400" + $"00D83F3F00003F3F3F3F3F3F00AA8400" + $"FF00D8D83F3F00003F3F3F3F00AA8400" + $"FF00D8D8D800000000003F3F00AA8400" + $"FFFF00D8002BA3003F3F003F00AA00FF" + $"FFFF0084002B3100D8D8003F00AA00FF" + $"FF0000002B2B00D8D8D8D80000AA00FF" + $"FF00A3312B31008484D8D80000AA00FF" + $"FF002B2B2B00FF000084848400AA000D" + $"FFFF002B3100FFFFFF0000840000000D" + $"FFFFFF0000FFFFFFFFFFFF0000000DFF" +}; + +resource(R_MICN_MailDirIcon) #'MICN' array { + $"FFFFFFFFFF0000FFFFFFFFFFFFFFFFFF" + $"FFFFFFFF0084840000FFFF0000FFFFFF" + $"FFFFFF000000AA84840000D8D80000FF" + $"FFFFFF003F1F0000AA84848484D8D800" + $"0000FF003F3F3F1E0000AA8484848400" + $"003F00003F3F3F3F3F1E0000AA848400" + $"00D83F3F00003F3F3F3F3F3F00AA8400" + $"FF00D8D83F3F00003F3F3F3F00AA8400" + $"FF00D8D8D8D83F3F00003F3F00AA8400" + $"FFFF00D8D8D8D8D83F3F003F00AA00FF" + $"0000000000000000D8D8003F00AA00FF" + $"0089FCFCFCF92A00D8D8D80000AA00FF" + $"003FFC8989F9F90084D8D80000AA00FF" + $"003FFC0909FCFC000084848400AA000D" + $"003FFCFCFCFCFC00FF0000840000000D" + $"0000000000000000FFFFFF0000000DFF" +}; + +resource(R_MICN_QueryDirIcon) #'MICN' array { + $"FFFFFFFFFF0000FFFFFFFFFFFFFFFFFF" + $"FFFFFFFF0084840000FFFF0000FFFFFF" + $"FFFFFF000000AA84840000D8D80000FF" + $"FFFFFF003F1F0000AA84848484D8D800" + $"0000FF003F3F3F1E0000AA8484848400" + $"003F00003F3F3F3F3F1E0000AA848400" + $"00D83F3F00003F3F3F3F3F3F00AA8400" + $"FF00D8D83F3F00003F3F3F3F00AA8400" + $"FF00D80000003F3F00003F3F00AA8400" + $"FFFF00123F1200003F3F003F00AA00FF" + $"FF00123F1203AB00D8D8003F00AA00FF" + $"00033F1203ABAB00D8D8D80000AA00FF" + $"00840303ABAB008484D8D80000AA00FF" + $"0084D184AB00FF000084848400AA000D" + $"0000848400FFFFFFFF0000840000000D" + $"FFFF0000FFFFFFFFFFFFFF0000000DFF" +}; + +resource(R_MICN_SpoolFileIcon) #'MICN' array { + $"FFFFFFFFFFFFFF00010001FFFFFFFFFF" + $"FFFFFFFFFFFF012B2B2B2B05FFFFFFFF" + $"FF0100FFFF002B2B00332B2B00FFFFFF" + $"00FFFF00FF01EB2B2B2B2B2F01FFFFFF" + $"FFFFFF01FF0000EB2A2F2F04FFFFFFFF" + $"FFFFFF00003F051C18181C02FFFFFFFF" + $"FFFFFF000060071E1C1C180100FFFFFF" + $"FFFF003F3F00081C18181C01600000FF" + $"FF003F603F012B1E1C1D182B31600000" + $"0060606060332A2B18182B2F310000AE" + $"AE0000878787F72A2B2B2F3101870000" + $"FFAE0000008787F733F7F7008700AEAE" + $"FFFFAE00AE0000AEAEAE000000AEFFFF" + $"FFFFFFAE0087AE00AE00AEAEFFFFFFFF" + $"FFFFFFFFAE00000000AEAEFFFFFFFFFF" + $"FFFFFFFFFFAE00AEFFAEFFFFFFFFFFFF" +}; + +resource(R_MICN_GenericPrinterIcon) #'MICN' array { + $"FFFFFFFFFFFF0000000000FFFFFFFFFF" + $"FFFFFFFFFF003F1711FE110000FFFFFF" + $"FFFFFFFF003F1711FEFEFEFE1100FFFF" + $"FFFFFF003F1711FDFDFDFEFEFE1000FF" + $"FFFF003F1711626262FDFDFD101700FF" + $"FF003F171189896262626210171700FF" + $"003F17171711108989621017170B00FF" + $"003F3F1717171711101017170B0B000F" + $"0011113F3F1717171717170B0B0B000F" + $"00111110113F3F1617170B0B0B000F0F" + $"00103F171110113F170B0B0B000F0FFF" + $"003F1E3F171711110B0B0B000F0FFFFF" + $"000011103F3F1B110B0B000F0FFFFFFF" + $"FFFF0000101103110B000F0FFFFFFFFF" + $"FFFFFFFF00000606040F0FFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" +}; + +resource(R_MICN_DevelopDirIcon) #'MICN' array { + $"FFFFFFFFFF0000FFFFFFFFFFFFFFFFFF" + $"FFFFFFFF0084840000FFFF0000FFFFFF" + $"FFFFFF000000AA84840000D8D80000FF" + $"FFFFFF003F1F0000AA84848484D8D800" + $"0000FF003F3F3F1E0000AA8484848400" + $"003F00003F3F3F3F3F1E0000AA848400" + $"00D83F3F00003F3F3F3F3F3F00AA8400" + $"FF00D8D83F3F00003F3F3F3F00AA8400" + $"FF00D80000D83F3F00003F3F00AA8400" + $"FFFF00151500D8D83F3F003F00AA00FF" + $"00FF00000000D800D8D8003F00AA00FF" + $"FF00A35A2AEB00D8D8D8D80000AA00FF" + $"00005A2A2A2A000084D8D80000AA00FF" + $"FF00A32A2A2A00000084848400AA000D" + $"0000A32A2AEB0000FF0000840000000D" + $"FFFF00000000FFFFFFFFFF0000000DFF" +}; + +resource(R_MICN_DownloadDirIcon) #'MICN' array { + $"FFFFFFFFFF0000FFFFFFFFFFFFFFFFFF" + $"FFFFFFFF0084840000FFFF0000FFFFFF" + $"FFFFFF000000AA84840000D8D80000FF" + $"FFFFFF003F1F0000AA84848484D8D800" + $"0000FF003F3F3F1E0000AA8484848400" + $"003F00003F3F3F3F3F1E0000AA848400" + $"00D83F3F00003F3F3F3F3F3F00AA8400" + $"FF00D8D83F3F00003F3F3F3F00AA8400" + $"FF00D8D8D800003F00003F3F00AA8400" + $"00000000003B56003F3F003F00AA00FF" + $"00F23F0056563B00D8D8003F00AA00FF" + $"00F23F00565600D8D8D8D80000AA00FF" + $"00F2F2000000008484D8D80000AA00FF" + $"00F2141414F200000084848400AA000D" + $"00F214F214F200FFFF0000840000000D" + $"FF000000000000FFFFFFFF0000000DFF" +}; + +resource(R_MICN_PersonDirIcon) #'MICN' array { + $"FFFFFFFFFF0000FFFFFFFFFFFFFFFFFF" + $"FFFFFFFF0084840000FFFF0000FFFFFF" + $"FFFFFF000000AA84840000D8D80000FF" + $"FFFFFF003F1F0000AA84848484D8D800" + $"0000FF003F3F3F1E0000AA8484848400" + $"003F00003F3F3F3F3F1E0000AA848400" + $"00D83F3F00003F3F3F3F3F3F00AA8400" + $"FF00D8D83F3F00003F3F3F3F00AA8400" + $"FF00D80000D83F3F00003F3F00AA8400" + $"FFFF003F3F00D8D83F3F003F00AA00FF" + $"FF00FA000000D8D8D8D8003F00AA00FF" + $"FF00F9F9FA7D00D8D8D8D80000AA00FF" + $"FF00F9F9F97D008484D8D80000AA00FF" + $"FFFF00F9F97D00000084848400AA000D" + $"FFFF00F9F900FFFFFF0000840000000D" + $"FFFFFF000000FFFFFFFFFF0000000DFF" +}; + +resource(R_MICN_UtilDirIcon) #'MICN' array { + $"FFFFFFFFFF0000FFFFFFFFFFFFFFFFFF" + $"FFFFFFFF0084840000FFFF0000FFFFFF" + $"FFFFFF000000AA84840000D8D80000FF" + $"FFFFFF003F1F0000AA84848484D8D800" + $"0000FF003F3F3F1E0000AA8484848400" + $"003F00003F3F3F3F3F1E0000AA848400" + $"00D83F3F00003F3F3F3F3F3F00AA8400" + $"FF00D8D83F3F00003F3F3F3F00AA8400" + $"FF00D8D800003F3F00003F3F00AA8400" + $"FFFF00001600D8D83F3F003F00AA00FF" + $"FFFF000016160000D8D8003F00AA00FF" + $"FF00001616161600D8D8D80000AA00FF" + $"003F16160000000084D8D80000AA00FF" + $"00001616001307000084848400AA000D" + $"FFFF001600343400FF0000840000000D" + $"FFFF000000000000FFFFFF0000000DFF" +}; + +resource(R_MICN_ConfigDirIcon) #'MICN' array { + $"FFFFFFFFFF0000FFFFFFFFFFFFFFFFFF" + $"FFFFFFFF0084840000FFFF0000FFFFFF" + $"FFFFFF000000AA84840000D8D80000FF" + $"FFFFFF003F1F0000AA84848484D8D800" + $"0000FF003F3F3F1E0000AA8484848400" + $"003F00003F3F3F3F3F1E0000AA848400" + $"00D83F3F00003F3F3F3F3F3F00AA8400" + $"FF00D8D83F3F00003F3F3F3F00AA8400" + $"FF00D8D8D8D83F3F00003F3F00AA8400" + $"00000000000000D83F3F003F00AA00FF" + $"002A043604FA00D8D8D8003F00AA00FF" + $"00040404040400D8D8D8D80000AA00FF" + $"0098043F0436008484D8D80000AA00FF" + $"00040404040400000084848400AA000D" + $"00360420042A00FFFF0000840000000D" + $"00000000000000FFFFFFFF0000000DFF" +}; + +resource(R_MSGG_RedBarberPoleBits) archive(, 0x00000000) BBitmap { + "_frame" = rect { 0.0, 0.0, 8.0, 17.0 }, + "_cspace" = 4, + "_bmflags" = 1, + "_rowbytes" = 12, + "_data" = array { + $"3F3F2A2C2A3F3F3F2A2C3F3F3F2A2C2A3F3F3F2A2C2A3F3F2A2C2A3F3F3F2A2C" + $"2A3F3F3F2C2A3F3F3F2A2C2A3F3F3F3F2A3F3F3F2A2C2A3F3F3F3F3F3F3F3F2A" + $"2C2A3F3F3F2A3F3F3F3F2A2C2A3F3F3F2A2C3F3F3F2A2C2A3F3F3F2A2C2A3F3F" + $"2A2C2A3F3F3F2A2C2A3F3F3F2C2A3F3F3F2A2C2A3F3F3F3F2A3F3F3F2A2C2A3F" + $"3F3F3F3F3F3F3F2A2C2A3F3F3F2A3F3F3F3F2A2C2A3F3F3F2A2C3F3F3F2A2C2A" + $"3F3F3F2A2C2A3F3F2A2C2A3F3F3F2A2C2A3F3F3F2C2A3F3F3F2A2C2A3F3F3F3F" + $"2A3F3F3F2A2C2A3F3F3F3F3F3F3F3F2A2C2A3F3F3F2A3F3F" + } +}; + +resource(R_BarberPoleBitmap) archive(, 0x00000000) BBitmap { + "_frame" = rect { 0.0, 0.0, 7.0, 17.0 }, + "_cspace" = 4, + "_bmflags" = 1, + "_rowbytes" = 8, + "_data" = array { + $"132721211B1B15272727213F1B1B272727271B3F1B21272727151B3F21212713" + $"13151B212121151313152121211B1513132721211B1B15272727213F1B1B2727" + $"27271B3F1B21272727151B3F2121271313151B212121151313152121211B1513" + $"132721211B1B15272727213F1B1B272727271B3F1B21272727151B3F21212713" + $"13151B212121151313152121211B1513" + } +}; + +resource(R_MoveStatusBitmap) archive(, 0x00000000) BBitmap { + "_frame" = rect { 0.0, 0.0, 23.0, 24.0 }, + "_cspace" = 4, + "_bmflags" = 1, + "_rowbytes" = 24, + "_data" = array { + $"1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B151B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B" + $"1B1B1A1B1B1B151B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A0E1A1B" + $"1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B0E1A1B1B1B1A1B1B151B1B1A" + $"1B1B1B1A1B1B1B1A0D0C1B1B1A1B1B1B1A1B1B151B1B1A1B1B1B1A1B1B1B1A1B" + $"0C3F0C0D1B1B1B1A1B1B0D1B1B1A1B1B1B1A1B1B1B1A1B1B0CFD3FFD0C0D1B1B" + $"1B0D1B1A1B1B1B1A1B1B1B1A1B1B1B1A0D3FFD3FFDFD0C0D1B1B1B1A1B1B1B1A" + $"1B1B1B1A1B1B1B1A0DFD3FFD3FFDFDFD0C0D1B1B1B1A1B1B1B1A1B1B1B00001A" + $"0D3FFD3FFDFDFDFDFDFD0A1B1A1B1B1B1A1B1B29000084010DFD3FFDFDFDFDFD" + $"FDFA0A1B1A1B1B1529001B00848484840E1FFDFDFDFDFDFDFDFA081B1A1B151B" + $"29D80000848484840EFDFDFDFAFAFAFAFAFA001A1B0D1B1B1B003FD80C118484" + $"0100FAFAFAFAFAFAFAFA001A0E1A1B1B1B00D83FD83F0D0D84840100FAFAFAFA" + $"FAFA001A1B1B1B1A1B003FD83FD8D81F0E9084840000FAFAFAFA001A1B1B1B1A" + $"1B00D83FD83FD8D8D8D8890E850B0100FAFA001A1B1B1B1A1B1B00D8D8D8D83F" + $"D8D8D80B8409001A0000001B1B1B1A1B1B1B00D8D8E5D8D8D8D8D80C84001A1B" + $"1B1B1A1B1B1B1A1B1B1B00D8D8E5D8D8D8D8D8D80B001B1B1B1A1B1B1B1A1B1B" + $"1B1A000084D8E5D9F8D8D8D804000F111B1B1A1B1B1B1A1B1B1B1A1B000084D8" + $"D8D8D8E529000F0F1B1B1B1A1B1B1B1A1B1B1B1A1B1B000084D8D8E529050F1B" + $"1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B000084D800111B1B1A1B1B1B1A1B1B1B" + $"1A1B1B1B1A1B1B1B1A1B0000001B1B1A1B1B1B1A1B1B1B1A" + } +}; + +resource(R_CopyStatusBitmap) archive(, 0x00000000) BBitmap { + "_frame" = rect { 0.0, 0.0, 23.0, 24.0 }, + "_cspace" = 4, + "_bmflags" = 1, + "_rowbytes" = 24, + "_data" = array { + $"1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B" + $"1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A0D0C1B1B1A1B1B" + $"1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B0D1B0D3F0C0D1B1B1B1A1B1B1B1A1B1B1B" + $"1A1B1B1B1A1B0D1B1B0CFD3FFD0C0D1B1B1B1A1B1B1B1A1B1B1B1A1B1B151B1B" + $"1A0D3FFD3FFDFD0C0D1B1B1B1A1B1B1B1A1B1B1B151B1A1B1B0CFD3FFD3FFDFD" + $"FD0C0D1B1B1B1A1B1B1B1A1B1B1B1A1B1B0C3FFD3FFDFDFDFDFDFD0A1B1A1B1B" + $"1B1A1B1B1B1A1B1B1B0CFD3FFDFDFDFDFD0DFA0A1B1A1B1B1B1A0D0C1B1B1A1B" + $"1B0C3FFDFDFDFDFD0DFDFA081B601C1A1B1B0C1FAFAF1B1B1B0CFDFDFDFAFA15" + $"FAFAFA001A1B1B1B1A1B0CFD3FFD0C0D1B0000FAFAFA15FAFAFAFA001A1B1B1B" + $"1A1B0C3FFD3FFDFD0C0D1B0000FAFAFAFAFAFA000E111B1B1B1A0DFD3FFD3FFD" + $"FDFD0C0D1B2900FAFAFAFA000F0F1B1B1B1A0D3FFD3FFDFDFDFDFDFD0A1B1A29" + $"00FAFA000F1B1A1B1B1B0CFD3FFDFDFDFDFDFDFA0A1B1A1B1B2900001B1A1B1B" + $"1B1A0D3FFDFDFDFDFDFDFDFA081B601C1A1B1B1B1A1B1B1B1A1BB0FEFDFDFAFA" + $"FAFAFAFA001A1B1B1B1A0E1A1B1B1B1A1B1B0000FAFAFAFAFAFAFAFA001A1B1B" + $"1B0D1B1A1B1B1B1A1B1B1B1A0000FAFAFAFAFAFA001A1B1B0D1B1B1A1B1B1B1A" + $"1B1B1B1A1B1B0000FAFAFAFA000F111B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B" + $"2900FAFA000F0F1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B0000000F1B1A" + $"1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A" + $"1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A" + } +}; + +resource(R_TrashStatusBitmap) archive(, 0x00000000) BBitmap { + "_frame" = rect { 0.0, 0.0, 23.0, 24.0 }, + "_cspace" = 4, + "_bmflags" = 1, + "_rowbytes" = 24, + "_data" = array { + $"1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B" + $"1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B" + $"1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B" + $"1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B" + $"1A1B0000001B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B0000081000001A" + $"1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A000000090F121100001B1B1A1B1B1B1A" + $"1B1B1B1A1B1B1B00000000080F1211121100001B1B1A1B1B1B1A1B1B1B1A0000" + $"000000090F1211120F00001B1A1B1B1B1A1B1B1B1A000F0F000000091011120F" + $"00001B1B1A1B1B1B1A1B1B1B1A0000050F0F00090F11100400001B1A1B1B1B1A" + $"1B1B1B1A1B1B000000040F100E100000001A1B1B1B1A1B1B1B1A1B1B1B1A00B5" + $"000000040F0000B5001B1B1A1B1B1B1A1B1B1B1A1B1B1B00B5B5B5000000B5B5" + $"001A1B1B1B1A1B1B1B1A1B1B1B1A1B006E6EB5B5B5B5B5B5001A1B1B1B1A1B1B" + $"1B1A1B1B1B1A1B006E6E6E6EB5B5B5000C0A0B1B1B1A1B1B1B1A1B1B1B1A1B1B" + $"006E6E41B5B5B5000A0B0B1B1B1B1A1B1B1B1A1B1B1B1A1B006E6E41B5B5290A" + $"0B0B1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B000041B5000A0B0B1B1B1B1A1B1B1B" + $"1A1B1B1B1A1B1B1B1A1B1B00000A0B0B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B" + $"1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B" + $"1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B" + $"1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B1A1B1B1B" + } +}; + + +resource(R_ResBackNavActive) archive(, 0x00000000) BBitmap { + "_frame" = rect { 0.0, 0.0, 18.0, 16.0 }, + "_cspace" = 4, + "_bmflags" = 1, + "_rowbytes" = 20, + "_data" = array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3FFFFFFFFFFFFFFF3B3B18FFFF" + $"FFFFFFFFFFFFFF3FFFFFFFFFFFFF3B353B1418FFFFFFFFFFFFFFFF3FFFFFFFFF" + $"FF3B35353B1418FFFFFFFFFFFFFFFF3FFFFFFFFF3B3435353B3B3B3B3B3B3B3B" + $"18FFFF3FFFFFFF3B34343535363637373737373B1418FF3FFFFF3B3434343535" + $"363637373737373B1418FF3FFF3B343434353536363637373737373B1418FF3F" + $"FF183B3535353536363637373737373B1418FF3FFFFF183B3536363636373737" + $"3737373B1418FF3FFFFFFF183B3636373B3B3B3B3B3B3B3B1418FF3FFFFFFFFF" + $"183B37373B141414141414141418FF3FFFFFFFFFFF183B373B14181818181818" + $"18FFFF3FFFFFFFFFFFFF183B3B1418FFFFFFFFFFFFFFFF3FFFFFFFFFFFFFFF18" + $"141418FFFFFFFFFFFFFFFF3FFFFFFFFFFFFFFFFF1818FFFFFFFFFFFFFFFFFF3F" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3F" + } +}; + +resource(R_ResBackNavInactive) archive(, 0x00000000) BBitmap { + "_frame" = rect { 0.0, 0.0, 18.0, 16.0 }, + "_cspace" = 4, + "_bmflags" = 1, + "_rowbytes" = 20, + "_data" = array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3FFFFFFFFFFFFFFF0000FFFFFF" + $"FFFFFFFFFFFFFF3FFFFFFFFFFFFF001800FFFFFFFFFFFFFFFFFFFF3FFFFFFFFF" + $"FF00181800FFFFFFFFFFFFFFFFFFFF3FFFFFFFFF001C18180000000000000000" + $"FFFFFF3FFFFFFF001C1C18181515111111111100FFFFFF3FFFFF001C1C1C1818" + $"1515111111111100FFFFFF3FFF001C1C1C1818151515111111111100FFFFFF3F" + $"FFFF0018181818151515111111111100FFFFFF3FFFFFFF001815151515111111" + $"11111100FFFFFF3FFFFFFFFF001515110000000000000000FFFFFF3FFFFFFFFF" + $"FF00111100FFFFFFFFFFFFFFFFFFFF3FFFFFFFFFFFFF001100FFFFFFFFFFFFFF" + $"FFFFFF3FFFFFFFFFFFFFFF0000FFFFFFFFFFFFFFFFFFFF3FFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFF3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3F" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3F" + } +}; + +resource(R_ResForwNavActive) archive(, 0x00000000) BBitmap { + "_frame" = rect { 0.0, 0.0, 18.0, 16.0 }, + "_cspace" = 4, + "_bmflags" = 1, + "_rowbytes" = 20, + "_data" = array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3FFFFFFFFFFFFFFFFF3B3B18FF" + $"FFFFFFFFFFFFFF3FFFFFFFFFFFFFFFFF3B353B18FFFFFFFFFFFFFF3FFFFFFFFF" + $"FFFFFFFF3B35363B18FFFFFFFFFFFF3FFF3B3B3B3B3B3B3B3B3536363B18FFFF" + $"FFFFFF3FFF3B34343434353535363636373B18FFFFFFFF3FFF3B343434353535" + $"3536363637373B18FFFFFF3FFF3B343435353535363636373737373B18FFFF3F" + $"FF3B3535353535363636373737373B141418FF3FFF3B35353536363636373737" + $"373B141418FFFF3FFF3B3B3B3B3B3B3B3B3737373B141418FFFFFF3FFF181414" + $"141414143B37373B141418FFFFFFFF3FFFFF1818181818183B373B141418FFFF" + $"FFFFFF3FFFFFFFFFFFFFFFFF3B3B141418FFFFFFFFFFFF3FFFFFFFFFFFFFFFFF" + $"18141418FFFFFFFFFFFFFF3FFFFFFFFFFFFFFFFFFF1818FFFFFFFFFFFFFFFF3F" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3F" + } +}; + +resource(R_ResForwNavInactive) archive(, 0x00000000) BBitmap { + "_frame" = rect { 0.0, 0.0, 18.0, 16.0 }, + "_cspace" = 4, + "_bmflags" = 1, + "_rowbytes" = 20, + "_data" = array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3FFFFFFFFFFFFFFFFF0000FFFF" + $"FFFFFFFFFFFFFF3FFFFFFFFFFFFFFFFF001800FFFFFFFFFFFFFFFF3FFFFFFFFF" + $"FFFFFFFF00181500FFFFFFFFFFFFFF3FFF000000000000000018151500FFFFFF" + $"FFFFFF3FFF001C1C1C1C1818181515151100FFFFFFFFFF3FFF001C1C1C181818" + $"18151515111100FFFFFFFF3FFF001C1C181818181515151111111100FFFFFF3F" + $"FF0018181818181515151111111100FFFFFFFF3FFF0018181815151515111111" + $"1100FFFFFFFFFF3FFF000000000000000011111100FFFFFFFFFFFF3FFFFFFFFF" + $"FFFFFFFF00111100FFFFFFFFFFFFFF3FFFFFFFFFFFFFFFFF001100FFFFFFFFFF" + $"FFFFFF3FFFFFFFFFFFFFFFFF0000FFFFFFFFFFFFFFFFFF3FFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFF3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3F" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3F" + } +}; + +resource(R_ResUpNavActive) archive(, 0x00000000) BBitmap { + "_frame" = rect { 0.0, 0.0, 17.0, 16.0 }, + "_cspace" = 4, + "_bmflags" = 1, + "_rowbytes" = 20, + "_data" = array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3F3FFFFFFFFFFFFFFFFF00FFFFFF" + $"FFFFFFFFFFFF3F3FFFFFFFFFFFFFFF00E50018FFFFFFFFFFFFFF3F3FFFFFFFFF" + $"FFFF00E5BDBD0018FFFFFFFFFFFF3F3FFFFFFFFFFF00E5BDBDBDBD0018FFFFFF" + $"FFFF3F3FFFFFFFFF00E5BDBDBDBDBDBD0018FFFFFFFF3F3FFFFFFF00E5BDBDBD" + $"BDBDBDBDBD0018FFFFFF3F3FFFFF00E5E5E5BDBDBDBDBD9898980018FFFF3F3F" + $"FF00000000E5BDBDBDBDBD980000000018FF3F3FFFFF141400E5BDBDBDBDBD98" + $"00141418FFFF3F3FFFFFFF1800E5BDBDBDBDBD98001418FFFFFF3F3FFFFFFFFF" + $"00E5BDBDBDBDBD98001418FFFFFF3F3FFFFFFFFF00E5989898989898001418FF" + $"FFFF3F3FFFFFFFFF0000000000000000001418FFFFFF3F3FFFFFFFFF18141414" + $"141414141418FFFFFFFF3F3FFFFFFFFFFF181818181818181818FFFFFFFF3F3F" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3F3F" + } +}; + +resource(R_ResUpNavInactive) archive(, 0x00000000) BBitmap { + "_frame" = rect { 0.0, 0.0, 17.0, 16.0 }, + "_cspace" = 4, + "_bmflags" = 1, + "_rowbytes" = 20, + "_data" = array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3F3FFFFFFFFFFFFFFFFF00FFFFFF" + $"FFFFFFFFFFFF3F3FFFFFFFFFFFFFFF001C00FFFFFFFFFFFFFFFF3F3FFFFFFFFF" + $"FFFF001C171700FFFFFFFFFFFFFF3F3FFFFFFFFFFF001C1717171700FFFFFFFF" + $"FFFF3F3FFFFFFFFF001C17171717171700FFFFFFFFFF3F3FFFFFFF001C171717" + $"171717171700FFFFFFFF3F3FFFFF001C1C1C171717171712121200FFFFFF3F3F" + $"FF000000001C17171717171200000000FFFF3F3FFFFFFFFF001C171717171712" + $"00FFFFFFFFFF3F3FFFFFFFFF001C17171717171200FFFFFFFFFF3F3FFFFFFFFF" + $"001C17171717171200FFFFFFFFFF3F3FFFFFFFFF001C12121212121200FFFFFF" + $"FFFF3F3FFFFFFFFF000000000000000000FFFFFFFFFF3F3FFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFF3F3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3F3F" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3F3F" + } +}; + +resource(R_ResBackNavActiveSel) archive(, 0x00000000) BBitmap { + "_frame" = rect { 0.0, 0.0, 18.0, 16.0 }, + "_cspace" = 4, + "_bmflags" = 1, + "_rowbytes" = 20, + "_data" = array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3FFFFFFFFFFFFFFF3B3BFFFFFF" + $"FFFFFFFFFFFFFF3FFFFFFFFFFFFF3B353BFFFFFFFFFFFFFFFFFFFF3FFFFFFFFF" + $"FF3B35353BFFFFFFFFFFFFFFFFFFFF3FFFFFFFFF3B3435353B3B3B3B3B3B3B3B" + $"FFFFFF3FFFFFFF3B34343535363637373737373BFFFFFF3FFFFF3B3434343535" + $"363637373737373BFFFFFF3FFF3B343434353536363637373737373BFFFFFF3F" + $"FFFF3B3535353536363637373737373BFFFFFF3FFFFFFF3B3536363636373737" + $"3737373BFFFFFF3FFFFFFFFF3B3636373B3B3B3B3B3B3B3BFFFFFF3FFFFFFFFF" + $"FF3B37373BFFFFFFFFFFFFFFFFFFFF3FFFFFFFFFFFFF3B373BFFFFFFFFFFFFFF" + $"FFFFFF3FFFFFFFFFFFFFFF3B3BFFFFFFFFFFFFFFFFFFFF3FFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFF3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3F" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3F" + } +}; + +resource(R_ResForwNavActiveSel) archive(, 0x00000000) BBitmap { + "_frame" = rect { 0.0, 0.0, 18.0, 16.0 }, + "_cspace" = 4, + "_bmflags" = 1, + "_rowbytes" = 20, + "_data" = array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3FFFFFFFFFFFFFFFFF3B3BFFFF" + $"FFFFFFFFFFFFFF3FFFFFFFFFFFFFFFFF3B353BFFFFFFFFFFFFFFFF3FFFFFFFFF" + $"FFFFFFFF3B35363BFFFFFFFFFFFFFF3FFF3B3B3B3B3B3B3B3B3536363BFFFFFF" + $"FFFFFF3FFF3B34343434353535363636373BFFFFFFFFFF3FFF3B343434353535" + $"3536363637373BFFFFFFFF3FFF3B343435353535363636373737373BFFFFFF3F" + $"FF3B3535353535363636373737373BFFFFFFFF3FFF3B35353536363636373737" + $"373BFFFFFFFFFF3FFF3B3B3B3B3B3B3B3B3737373BFFFFFFFFFFFF3FFFFFFFFF" + $"FFFFFFFF3B37373BFFFFFFFFFFFFFF3FFFFFFFFFFFFFFFFF3B373BFFFFFFFFFF" + $"FFFFFF3FFFFFFFFFFFFFFFFF3B3BFFFFFFFFFFFFFFFFFF3FFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFFFF3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3F" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3F" + } +}; + +resource(R_ResUpNavActiveSel) archive(, 0x00000000) BBitmap { + "_frame" = rect { 0.0, 0.0, 17.0, 16.0 }, + "_cspace" = 4, + "_bmflags" = 1, + "_rowbytes" = 20, + "_data" = array { + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3F3FFFFFFFFFFFFFFFFF00FFFFFF" + $"FFFFFFFFFFFF3F3FFFFFFFFFFFFFFF00E500FFFFFFFFFFFFFFFF3F3FFFFFFFFF" + $"FFFF00E5BDBD00FFFFFFFFFFFFFF3F3FFFFFFFFFFF00E5BDBDBDBD00FFFFFFFF" + $"FFFF3F3FFFFFFFFF00E5BDBDBDBDBDBD00FFFFFFFFFF3F3FFFFFFF00E5BDBDBD" + $"BDBDBDBDBD00FFFFFFFF3F3FFFFF00E5E5E5BDBDBDBDBD98989800FFFFFF3F3F" + $"FF00000000E5BDBDBDBDBD9800000000FFFF3F3FFFFFFFFF00E5BDBDBDBDBD98" + $"00FFFFFFFFFF3F3FFFFFFFFF00E5BDBDBDBDBD9800FFFFFFFFFF3F3FFFFFFFFF" + $"00E5BDBDBDBDBD9800FFFFFFFFFF3F3FFFFFFFFF00E598989898989800FFFFFF" + $"FFFF3F3FFFFFFFFF000000000000000000FFFFFFFFFF3F3FFFFFFFFFFFFFFFFF" + $"FFFFFFFFFFFFFFFFFFFF3F3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3F3F" + $"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3F3F" + } +}; + +resource(R_ShareIcon) #'ICON' array { + $"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + $"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + $"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + $"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + $"ffffffffffffffffffffffffffffffffffffffffffffffff00000000ffffffff" + $"ffffffffffff000000000000000000000000000000ffff003fd9d9d90000ffff" + $"ffffffffff003f3f3f3f3f3f00003f3f3f3f3f3f3f00003fd9d9d9d9d9d90000" + $"ffffffffff003fd9d9d9d90084840000d9d90000d93f3faaaad9d9d9d9d9d983" + $"ffffffffff003fd9d9d9000000aa84840000d8d80000838383aaaad9d9d983aa" + $"ffffffffff1100000000003f3f0000aa84848484d8d8003fd93f3faaaa83aaaa" + $"ffffffffffff11000011003f3f3f3f0000aa8484848400d9d9d9d93f3faaaaaa" + $"ffffffffffffff003f00003f3f3f3f3f3f0000aa848400d9d9d9d9d9d983aa01" + $"ffffffffffffff00d83f3f00003f3f3f3f3f3f00aa8400d93f3fd9d9d9d9aa00" + $"ffffffffffffffff00d8d83f3f00003f3f3f3f00aa8400d93f3fd9d9d9d98300" + $"ffffffffffffffff00d8d8d8d83f3f00003f3f00aa8400d9d9d9d9d9d983aa01" + $"ffffffffffffffff0000d8d8d8d8d83f3f003f00aa00d9d9d9d9d9d983aa0011" + $"ffffffffffffff003f008484d8d8d8d8d8003f00aa00d9d9d9d9d983aa001111" + $"ffffffffffff003fd98300008484d8d8d8d80000aa00d9d9d9d983aa001111ff" + $"ffffffffff003fd98383003f00008484d8d80000aa00d9d9d983aa001111ffff" + $"ffffffff003fd98383003fd9d983000084848400aa0010d983aa001111ffffff" + $"ffffff003fd98383003fd9d98383003f0000840000001083aa001111ffffffff" + $"ffff003fd98383003fd9d98383003fd9d9830000001083aa001111ffffffffff" + $"ff003fd98383003fd9d98383003fd9d98383003fd983aa001111ffffffffffff" + $"ff00d98383003fd9d98383003fd9d98383003fd98383001111ffffffffffffff" + $"ffff0000003fd9d98383003fd9d98383003fd98383001111ffffffffffffffff" + $"ffffff003fd9d98383003fd9d98383003fd98383001111ffffffffffffffffff" + $"ffffff00d9838306003fd9d98383003fd98383001111ffffffffffffffffffff" + $"ffffffff0000001000d9d98383003fd98383001111ffffffffffffffffffffff" + $"ffffffffffffffffff000000003fd98300001111ffffffffffffffffffffffff" + $"ffffffffffffffffffffffffff000000111111ffffffffffffffffffffffffff" + $"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + $"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" +}; + +resource(R_MICN_ShareIcon) #'MICN' array { + $"ffffffffffffffffffffffffffffffff" + $"ffffffffffffffffffffffffffffffff" + $"ffffff000000000000000f0fdad900ff" + $"ffff0fdad9b08400d900da1783d9d983" + $"ffff1800000f0f078484d80fda17aaaa" + $"ffffff0f0f0f3f3f0f0784b0d9d983d1" + $"ffffffffb0d90f0f3f0faab03fd9d9d0" + $"ffffffff00d8d8d90f0f07d9d9d9aa08" + $"ffffff0f830084d8d80007d9d9aa0818" + $"ffff0f83d0da830084d10782aa0818ff" + $"ff0f83d0da83d0da830008aa0818ffff" + $"0f83d0da83d0da83d0da830818ffffff" + $"ff0fda83d0da83d0da830818ffffffff" + $"ffff0008b083d0da830818ffffffffff" + $"ffffffffffff0f001118ffffffffffff" + $"ffffffffffffffffffffffffffffffff" +}; + diff --git a/src/kits/tracker/TrackerInitialState.cpp b/src/kits/tracker/TrackerInitialState.cpp new file mode 100644 index 0000000000..d425b60ded --- /dev/null +++ b/src/kits/tracker/TrackerInitialState.cpp @@ -0,0 +1,671 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +// ToDo: +// add code to initialize a subset of the mime database, including +// important sniffer rules + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "pr_server.h" + +#include "Attributes.h" +#include "AttributeStream.h" +#include "BackgroundImage.h" +#include "Bitmaps.h" +#include "ContainerWindow.h" +#include "MimeTypes.h" +#include "FSUtils.h" +#include "QueryContainerWindow.h" +#include "Tracker.h" + +enum { + kForceLargeIcon = 0x1, + kForceMiniIcon = 0x2, + kForceShortDescription = 0x4, + kForceLongDescription = 0x8, + kForcePreferredApp = 0x10 +}; + + +const char *kAttrName = "META:name"; +const char *kAttrCompany = "META:company"; +const char *kAttrAddress = "META:address"; +const char *kAttrCity = "META:city"; +const char *kAttrState = "META:state"; +const char *kAttrZip = "META:zip"; +const char *kAttrCountry = "META:country"; +const char *kAttrHomePhone = "META:hphone"; +const char *kAttrWorkPhone = "META:wphone"; +const char *kAttrFax = "META:fax"; +const char *kAttrEmail = "META:email"; +const char *kAttrURL = "META:url"; +const char *kAttrGroup = "META:group"; +const char *kAttrNickname = "META:nickname"; + +const char *kNetPositiveSignature = "application/x-vnd.Be-NPOS"; + +namespace BPrivate { + +class ExtraAttributeLazyInstaller { +public: + ExtraAttributeLazyInstaller(const char *type); + ~ExtraAttributeLazyInstaller(); + + bool AddExtraAttribute(const char *publicName, const char *name, + uint32 type, bool viewable, bool editable, float width, + int32 alignment, bool extra); + + status_t InitCheck() const; +public: + BMimeType fMimeType; + BMessage fExtraAttrs; + bool fDirty; +}; + +} + +ExtraAttributeLazyInstaller::ExtraAttributeLazyInstaller(const char *type) + : fMimeType(type), + fDirty(false) +{ + if (fMimeType.InitCheck() == B_OK) + fMimeType.GetAttrInfo(&fExtraAttrs); +} + + +ExtraAttributeLazyInstaller::~ExtraAttributeLazyInstaller() +{ + if (fMimeType.InitCheck() == B_OK && fDirty) + fMimeType.SetAttrInfo(&fExtraAttrs); +} + +bool +ExtraAttributeLazyInstaller::AddExtraAttribute(const char *publicName, const char *name, + uint32 type, bool viewable, bool editable, float width, int32 alignment, + bool extra) +{ + for (int32 index = 0; ; index++) { + const char *oldPublicName; + if (fExtraAttrs.FindString("attr:public_name", index, &oldPublicName) != B_OK) + break; + + if (strcmp(oldPublicName, publicName) == 0) + // already got this extra atribute, no work left + return false; + } + + fExtraAttrs.AddString("attr:public_name", publicName); + fExtraAttrs.AddString("attr:name", name); + fExtraAttrs.AddInt32("attr:type", (int32)type); + fExtraAttrs.AddBool("attr:viewable", viewable); + fExtraAttrs.AddBool("attr:editable", editable); + fExtraAttrs.AddInt32("attr:width", (int32)width); + fExtraAttrs.AddInt32("attr:alignment", alignment); + fExtraAttrs.AddBool("attr:extra", extra); + + fDirty = true; + return true; +} + + +bool +TTracker::InstallMimeIfNeeded(const char *type, int32 bitsID, + const char *shortDescription, const char *longDescription, + const char *preferredAppSignature, uint32 forceMask) +{ + // used by InitMimeTypes - checks if a metamime of a given is + // installed and if it has all the specified attributes; if not, the + // whole mime type is installed and all attributes are set; nulls can + // be passed for attributes that don't matter; returns true if anything + // had to be changed + BBitmap largeIcon(BRect(0, 0, 31, 31), B_COLOR_8_BIT); + BBitmap miniIcon(BRect(0, 0, 15, 15), B_COLOR_8_BIT); + char tmp[B_MIME_TYPE_LENGTH]; + + BMimeType mime(type); + bool installed = mime.IsInstalled(); + + if (!installed + || (bitsID >= 0 && ((forceMask & kForceLargeIcon) + || mime.GetIcon(&largeIcon, B_LARGE_ICON) != B_OK)) + || (bitsID >= 0 && ((forceMask & kForceMiniIcon) + || mime.GetIcon(&miniIcon, B_MINI_ICON) != B_OK)) + || (shortDescription && ((forceMask & kForceShortDescription) + || mime.GetShortDescription(tmp) != B_OK)) + || (longDescription && ((forceMask & kForceLongDescription) + || mime.GetLongDescription(tmp) != B_OK)) + || (preferredAppSignature && ((forceMask & kForcePreferredApp) + || mime.GetPreferredApp(tmp) != B_OK))) { + + if (!installed) + mime.Install(); + + if (bitsID >= 0) { + if (GetTrackerResources()-> + GetIconResource(bitsID, B_LARGE_ICON, &largeIcon) == B_OK) + mime.SetIcon(&largeIcon, B_LARGE_ICON); + + if (GetTrackerResources()-> + GetIconResource(bitsID, B_MINI_ICON, &miniIcon) == B_OK) + mime.SetIcon(&miniIcon, B_MINI_ICON); + } + + if (shortDescription) + mime.SetShortDescription(shortDescription); + + if (longDescription) + mime.SetLongDescription(longDescription); + + if (preferredAppSignature) + mime.SetPreferredApp(preferredAppSignature); + + return true; + } + return false; +} + +void +TTracker::InitMimeTypes() +{ + InstallMimeIfNeeded(B_DIR_MIMETYPE, kResFolderIcon, + "Folder", "Folder container for file system items.", kTrackerSignature); + + InstallMimeIfNeeded(B_APP_MIME_TYPE, kResAppIcon, + "Be Application", "Generic Be Application executable.", kTrackerSignature); + + InstallMimeIfNeeded(B_FILE_MIMETYPE, kResFileIcon, + "Generic file", "Generic document file.", kTrackerSignature); + + InstallMimeIfNeeded(B_VOLUME_MIMETYPE, kResHardDiskIcon, + "Be Volume", "Disk volume.", kTrackerSignature); + + InstallMimeIfNeeded(B_QUERY_MIMETYPE, kResQueryIcon, + "Be Query", "Query to locate items on disks.", kTrackerSignature); + + InstallMimeIfNeeded(B_QUERY_TEMPLATE_MIMETYPE, kResQueryTemplateIcon, + "Be Query Template", "", kTrackerSignature); + + InstallMimeIfNeeded(B_LINK_MIMETYPE, kResBrokenLinkIcon, + "Symbolic Link", "Link to another item in the file system.", kTrackerSignature); + + InstallMimeIfNeeded(B_ROOT_MIMETYPE, kResBeBoxIcon, + "Be Root", "File system root.", kTrackerSignature); + + InstallMimeIfNeeded(B_BOOKMARK_MIMETYPE, kResBookmarkIcon, + "Bookmark", "Bookmark for a web page.", kNetPositiveSignature); + + { + // install a couple of extra fields for bookmark + + ExtraAttributeLazyInstaller installer(B_BOOKMARK_MIMETYPE); + installer.AddExtraAttribute("URL", "META:url", B_STRING_TYPE, true, true, + 170, B_ALIGN_LEFT, false); + installer.AddExtraAttribute("Keywords", "META:keyw", B_STRING_TYPE, true, true, + 130, B_ALIGN_LEFT, false); + installer.AddExtraAttribute("Title", "META:title", B_STRING_TYPE, true, true, + 130, B_ALIGN_LEFT, false); + } + + InstallMimeIfNeeded(B_PERSON_MIMETYPE, kResPersonIcon, + "Person", "Contact information for a person.", 0); + + { + ExtraAttributeLazyInstaller installer(B_PERSON_MIMETYPE); + installer.AddExtraAttribute("Contact Name", kAttrName, B_STRING_TYPE, true, true, + 120, B_ALIGN_LEFT, false); + installer.AddExtraAttribute("Company", kAttrCompany, B_STRING_TYPE, true, true, + 120, B_ALIGN_LEFT, false); + installer.AddExtraAttribute("Address", kAttrAddress, B_STRING_TYPE, true, true, + 120, B_ALIGN_LEFT, false); + installer.AddExtraAttribute("City", kAttrCity, B_STRING_TYPE, true, true, + 90, B_ALIGN_LEFT, false); + installer.AddExtraAttribute("State", kAttrState, B_STRING_TYPE, true, true, + 50, B_ALIGN_LEFT, false); + installer.AddExtraAttribute("Zip", kAttrZip, B_STRING_TYPE, true, true, + 50, B_ALIGN_LEFT, false); + installer.AddExtraAttribute("Country", kAttrCountry, B_STRING_TYPE, true, true, + 120, B_ALIGN_LEFT, false); + installer.AddExtraAttribute("E-mail", kAttrEmail, B_STRING_TYPE, true, true, + 120, B_ALIGN_LEFT, false); + installer.AddExtraAttribute("Home Phone", kAttrHomePhone, B_STRING_TYPE, true, true, + 90, B_ALIGN_LEFT, false); + installer.AddExtraAttribute("Work Phone", kAttrWorkPhone, B_STRING_TYPE, true, true, + 90, B_ALIGN_LEFT, false); + installer.AddExtraAttribute("Fax", kAttrFax, B_STRING_TYPE, true, true, + 90, B_ALIGN_LEFT, false); + installer.AddExtraAttribute("URL", kAttrURL, B_STRING_TYPE, true, true, + 120, B_ALIGN_LEFT, false); + installer.AddExtraAttribute("Group", kAttrGroup, B_STRING_TYPE, true, true, + 120, B_ALIGN_LEFT, false); + installer.AddExtraAttribute("Nickname", kAttrNickname, B_STRING_TYPE, true, true, + 120, B_ALIGN_LEFT, false); + } + + InstallMimeIfNeeded(B_PRINTER_SPOOL_MIMETYPE, kResSpoolFileIcon, + "Printer spool", "Printer spool file.", "application/x-vnd.Be-PRNT"); + + { +#if B_BEOS_VERSION_DANO + ExtraAttributeLazyInstaller installer(B_PRINTER_SPOOL_MIMETYPE); + installer.AddExtraAttribute("Status", PSRV_SPOOL_ATTR_STATUS, B_STRING_TYPE, true, false, + 60, B_ALIGN_LEFT, false); + installer.AddExtraAttribute("Page Count", PSRV_SPOOL_ATTR_PAGECOUNT, B_INT32_TYPE, true, false, + 40, B_ALIGN_RIGHT, false); + installer.AddExtraAttribute("Description", PSRV_SPOOL_ATTR_DESCRIPTION, B_STRING_TYPE, true, true, + 100, B_ALIGN_LEFT, false); + installer.AddExtraAttribute("Printer Name", PSRV_SPOOL_ATTR_PRINTER, B_STRING_TYPE, true, false, + 80, B_ALIGN_LEFT, false); + installer.AddExtraAttribute("Job creator type", PSRV_SPOOL_ATTR_MIMETYPE, B_ASCII_TYPE, true, false, + 60, B_ALIGN_LEFT, false); +#else + ExtraAttributeLazyInstaller installer(B_PRINTER_SPOOL_MIMETYPE); + installer.AddExtraAttribute("Page Count", "_spool/Page Count", B_INT32_TYPE, true, false, + 40, B_ALIGN_RIGHT, false); + installer.AddExtraAttribute("Description", "_spool/Description", B_ASCII_TYPE, true, true, + 100, B_ALIGN_LEFT, false); + installer.AddExtraAttribute("Printer Name", "_spool/Printer", B_ASCII_TYPE, true, false, + 80, B_ALIGN_LEFT, false); + installer.AddExtraAttribute("Job creator type", "_spool/MimeType", B_ASCII_TYPE, true, false, + 60, B_ALIGN_LEFT, false); +#endif + } + + InstallMimeIfNeeded(B_PRINTER_MIMETYPE, kResGenericPrinterIcon, + "Printer", "Printer queue.", kTrackerSignature /*application/x-vnd.Be-PRNT*/); + // for now set tracker as a default handler for the printer because we + // just want to open it as a folder +#if B_BEOS_VERSION_DANO + { + ExtraAttributeLazyInstaller installer(B_PRINTER_MIMETYPE); + installer.AddExtraAttribute("Driver", PSRV_PRINTER_ATTR_DRV_NAME, B_STRING_TYPE, true, false, + 120, B_ALIGN_LEFT, false); + installer.AddExtraAttribute("Transport", PSRV_PRINTER_ATTR_TRANSPORT, B_STRING_TYPE, true, false, + 60, B_ALIGN_RIGHT, false); + installer.AddExtraAttribute("Connection", PSRV_PRINTER_ATTR_CNX, B_STRING_TYPE, true, false, + 40, B_ALIGN_LEFT, false); + installer.AddExtraAttribute("Description", PSRV_PRINTER_ATTR_COMMENTS, B_STRING_TYPE, true, true, + 140, B_ALIGN_LEFT, false); + } +#endif +} + +void +TTracker::InstallIndices() +{ + BVolumeRoster roster; + BVolume volume; + + roster.Rewind(); + while (roster.GetNextVolume(&volume) == B_OK) { + if (volume.IsReadOnly() || !volume.IsPersistent() + || !volume.KnowsAttr() || !volume.KnowsQuery()) + continue; + InstallIndices(volume.Device()); + } +} + +void +TTracker::InstallIndices(dev_t device) +{ + status_t error = fs_create_index(device, kAttrQueryLastChange, B_INT32_TYPE, 0); + error = fs_create_index(device, "_trk/recentQuery", B_INT32_TYPE, 0); +} + +const int32 kDefaultQueryTemplateCount = 3; +extern const AttributeTemplate kDefaultQueryTemplate[]; +extern const AttributeTemplate kBookmarkQueryTemplate[]; +extern const AttributeTemplate kPersonQueryTemplate[]; +extern const AttributeTemplate kEmailQueryTemplate[]; + +void +TTracker::InstallDefaultTemplates() +{ + BNode node; + BString query(kQueryTemplates); + query += "/application_octet-stream"; + + if (!BContainerWindow::DefaultStateSourceNode(query.String(), &node, false)) + if (BContainerWindow::DefaultStateSourceNode(query.String(), &node, true)) { + AttributeStreamFileNode fileNode(&node); + AttributeStreamTemplateNode tmp(kDefaultQueryTemplate, 3); + fileNode << tmp; + } + + (query = kQueryTemplates) += "/application_x-vnd.Be-bookmark"; + if (!BContainerWindow::DefaultStateSourceNode(query.String(), &node, false)) + if (BContainerWindow::DefaultStateSourceNode(query.String(), &node, true)) { + AttributeStreamFileNode fileNode(&node); + AttributeStreamTemplateNode tmp(kBookmarkQueryTemplate, 3); + fileNode << tmp; + } + + (query = kQueryTemplates) += "/application_x-person"; + if (!BContainerWindow::DefaultStateSourceNode(query.String(), &node, false)) + if (BContainerWindow::DefaultStateSourceNode(query.String(), &node, true)) { + AttributeStreamFileNode fileNode(&node); + AttributeStreamTemplateNode tmp(kPersonQueryTemplate, 3); + fileNode << tmp; + } + + (query = kQueryTemplates) += "/text_x-email"; + if (!BContainerWindow::DefaultStateSourceNode(query.String(), &node, false)) + if (BContainerWindow::DefaultStateSourceNode(query.String(), &node, true)) { + AttributeStreamFileNode fileNode(&node); + AttributeStreamTemplateNode tmp(kEmailQueryTemplate, 3); + fileNode << tmp; + } +} + +static void +InstallTemporaryBackgroundImages(BNode *node, BMessage *message) +{ + int32 size = message->FlattenedSize(); + char *buffer = new char [size]; + message->Flatten(buffer, size); + node->WriteAttr(kBackgroundImageInfo, B_MESSAGE_TYPE, 0, buffer, (size_t)size); + delete [] buffer; +} + +static void +AddTemporaryBackgroundImages(BMessage *message, const char *imagePath, + BackgroundImage::Mode mode, BPoint offset, uint32 workspaces, bool eraseTextWidgets) +{ + message->AddString(kBackgroundImageInfoPath, imagePath); + message->AddInt32(kBackgroundImageInfoWorkspaces, (int32)workspaces); + message->AddInt32(kBackgroundImageInfoMode, mode); + message->AddBool(kBackgroundImageInfoEraseText, eraseTextWidgets); + message->AddPoint(kBackgroundImageInfoOffset, offset); +} + +static void +InstallTemporaryBackgroundImagesIfNeeded(BNode *node, const char *imagePath, + BackgroundImage::Mode mode, BPoint offset, uint32 workspaces, bool eraseTextWidgets) +{ + attr_info info; + if (node->GetAttrInfo(kBackgroundImageInfo, &info) != B_OK) { + BMessage message; + AddTemporaryBackgroundImages(&message, imagePath, mode, offset, workspaces, + eraseTextWidgets); + InstallTemporaryBackgroundImages(node, &message); + } +} + +void +TTracker::InstallTemporaryBackgroundImages() +{ + + BPath path; + FSFindTrackerSettingsDir(&path, false); + BString defaultFolderPath(path.Path()); + defaultFolderPath << '/' << kDefaultFolderTemplate << '/'; + + BNode node; + if (BContainerWindow::DefaultStateSourceNode(kDefaultFolderTemplate, &node, true)) + InstallTemporaryBackgroundImagesIfNeeded(&node, + (BString(defaultFolderPath) << "backgroundTexture.tga").String(), + BackgroundImage::kTiled, + BPoint(0, 0), 0xffffffff, false); + + BDirectory dir; + if (FSGetBootDeskDir(&dir) == B_OK) { + attr_info info; + if (dir.GetAttrInfo(kBackgroundImageInfo, &info) != B_OK) { + + BMessage message; + AddTemporaryBackgroundImages(&message, + (BString(defaultFolderPath) << "bgdefault.tga").String(), + BackgroundImage::kScaledToFit, + BPoint(0, 0), 0xffffffff, true); + AddTemporaryBackgroundImages(&message, + (BString(defaultFolderPath) << "bg1.tga").String(), + BackgroundImage::kScaledToFit, + BPoint(0, 0), 0x00000001, true); + AddTemporaryBackgroundImages(&message, + (BString(defaultFolderPath) << "bg2.tga").String(), + BackgroundImage::kScaledToFit, + BPoint(0, 0), 0x00000002, true); + AddTemporaryBackgroundImages(&message, + (BString(defaultFolderPath) << "bg3.tga").String(), + BackgroundImage::kScaledToFit, + BPoint(0, 0), 0x00000004, true); + AddTemporaryBackgroundImages(&message, + (BString(defaultFolderPath) << "bg4.tga").String(), + BackgroundImage::kScaledToFit, + BPoint(0, 0), 0x00000008, true); + AddTemporaryBackgroundImages(&message, + (BString(defaultFolderPath) << "bg5.tga").String(), + BackgroundImage::kScaledToFit, + BPoint(0, 0), 0x00000010, true); + AddTemporaryBackgroundImages(&message, + (BString(defaultFolderPath) << "bg6.tga").String(), + BackgroundImage::kScaledToFit, + BPoint(0, 0), 0x00000020, true); + AddTemporaryBackgroundImages(&message, + (BString(defaultFolderPath) << "bg7.tga").String(), + BackgroundImage::kScaledToFit, + BPoint(0, 0), 0x00000040, true); + AddTemporaryBackgroundImages(&message, + (BString(defaultFolderPath) << "bg8.tga").String(), + BackgroundImage::kScaledToFit, + BPoint(0, 0), 0x00000080, true); + AddTemporaryBackgroundImages(&message, + (BString(defaultFolderPath) << "bg9.tga").String(), + BackgroundImage::kScaledToFit, + BPoint(0, 0), 0x00000100, true); + AddTemporaryBackgroundImages(&message, + (BString(defaultFolderPath) << "bg10.tga").String(), + BackgroundImage::kScaledToFit, + BPoint(0, 0), 0x00000200, true); + AddTemporaryBackgroundImages(&message, + (BString(defaultFolderPath) << "bg11.tga").String(), + BackgroundImage::kScaledToFit, + BPoint(0, 0), 0x00000400, true); + AddTemporaryBackgroundImages(&message, + (BString(defaultFolderPath) << "bg12.tga").String(), + BackgroundImage::kScaledToFit, + BPoint(0, 0), 0x00000800, true); + AddTemporaryBackgroundImages(&message, + (BString(defaultFolderPath) << "bg12.tga").String(), + BackgroundImage::kScaledToFit, + BPoint(0, 0), 0x00001000, true); + AddTemporaryBackgroundImages(&message, + (BString(defaultFolderPath) << "bg13.tga").String(), + BackgroundImage::kScaledToFit, + BPoint(0, 0), 0x00002000, true); + AddTemporaryBackgroundImages(&message, + (BString(defaultFolderPath) << "bg14.tga").String(), + BackgroundImage::kScaledToFit, + BPoint(0, 0), 0x00002000, true); + AddTemporaryBackgroundImages(&message, + (BString(defaultFolderPath) << "bg15.tga").String(), + BackgroundImage::kScaledToFit, + BPoint(0, 0), 0x00004000, true); + AddTemporaryBackgroundImages(&message, + (BString(defaultFolderPath) << "bg16.tga").String(), + BackgroundImage::kScaledToFit, + BPoint(0, 0), 0x00008000, true); + ::InstallTemporaryBackgroundImages(&dir, &message); + } + } +} + + +// the following templates are in big endian and we rely on the Tracker +// translation support to swap them on little endian machines +// +// in case there is an attribute (B_RECT_TYPE) that gets swapped by the media (unzip, +// file system endianness swapping, etc., the correct endianness for the +// correct machine has to be used here + +const BRect kDefaultFrame(40, 40, 500, 350); + +const AttributeTemplate kDefaultQueryTemplate[] = + /* /boot/home/config/settings/Tracker/DefaultQueryTemplates/application_octet-stream */ +{ + { /* default frame */ + kAttrWindowFrame, + B_RECT_TYPE, + 16, + (const char *)&kDefaultFrame + }, + { /* attr: _trk/viewstate */ + kAttrViewState_be, + B_RAW_TYPE, + 49, + "o^\365R\000\000\000\012Tlst\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\357\323\335RCSTR\000\000\000" + "\000\000\000\000\000\000" + }, + { /* attr: _trk/columns */ + kAttrColumns_be, + B_RAW_TYPE, + 223, + "O\362VR\000\000\000\025\000\000\000\004Name\000B \000\000C\021\000" + "\000\000\000\000\000\000\000\000\012_stat/name\000\357\323\335RCST" + "R\001\001O\362VR\000\000\000\025\000\000\000\004Path\000CH\000\000" + "Ca\000\000\000\000\000\000\000\000\000\011_trk/path\000\357_\174RC" + "STR\000\000O\362VR\000\000\000\025\000\000\000\004Size\000C\334\000" + "\000B$\000\000\000\000\000\001\000\000\000\012_stat/size\000\317\317" + "\306TOFFT\001\000O\362VR\000\000\000\025\000\000\000\010Modified\000" + "C\370\000\000C\012\000\000\000\000\000\000\000\000\000\016_stat/mo" + "dified\000]KmETIME\001\000" + } +}; + +const AttributeTemplate kBookmarkQueryTemplate[] = + /* /boot/home/config/settings/Tracker/DefaultQueryTemplates/application_x-vnd.Be-bookmark */ +{ + { /* default frame */ + kAttrWindowFrame, + B_RECT_TYPE, + 16, + (const char *)&kDefaultFrame + }, + { /* attr: _trk/viewstate */ + kAttrViewState_be, + B_RAW_TYPE, + 49, + "o^\365R\000\000\000\012Tlst\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000w\373\175RCSTR\000\000\000" + "\000\000\000\000\000\000" + }, + { /* attr: _trk/columns */ + kAttrColumns_be, + B_RAW_TYPE, + 163, + "O\362VR\000\000\000\025\000\000\000\005Title\000B \000\000C+\000\000" + "\000\000\000\000\000\000\000\012META:title\000w\373\175RCSTR\000\001" + "O\362VR\000\000\000\025\000\000\000\003URL\000Cb\000\000C\217\200\000" + "\000\000\000\000\000\000\000\010META:url\000\343[TRCSTR\000\001O\362" + "VR\000\000\000\025\000\000\000\010Keywords\000D\004\000\000C\002\000" + "\000\000\000\000\000\000\000\000\011META:keyw\000\333\363\334RCSTR" + "\000\001" + } +}; + +const AttributeTemplate kPersonQueryTemplate[] = + /* /boot/home/config/settings/Tracker/DefaultQueryTemplates/application_x-vnd.Be-bookmark */ +{ + { /* default frame */ + kAttrWindowFrame, + B_RECT_TYPE, + 16, + (const char *)&kDefaultFrame + }, + { /* attr: _trk/viewstate */ + kAttrViewState_be, + B_RAW_TYPE, + 49, + "o^\365R\000\000\000\012Tlst\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\357\323\335RCSTR\000\000\000" + "\000\000\000\000\000\000" + }, + { /* attr: _trk/columns */ + kAttrColumns_be, + B_RAW_TYPE, + 230, + "O\362VR\000\000\000\025\000\000\000\004Name\000B \000\000B\346\000" + "\000\000\000\000\000\000\000\000\012_stat/name\000\357\323\335RCST" + "R\001\001O\362VR\000\000\000\025\000\000\000\012Work Phone\000C*\000" + "\000B\264\000\000\000\000\000\000\000\000\000\013META:wphone\000C_" + "uRCSTR\000\001O\362VR\000\000\000\025\000\000\000\006E-mail\000C\211" + "\200\000B\272\000\000\000\000\000\000\000\000\000\012META:email\000" + "sW\337RCSTR\000\001O\362VR\000\000\000\025\000\000\000\007Company\000" + "C\277\200\000B\360\000\000\000\000\000\000\000\000\000\014META:com" + "pany\000CS\174RCSTR\000\001" + }, +}; + +const AttributeTemplate kEmailQueryTemplate[] = + /* /boot/home/config/settings/Tracker/DefaultQueryTemplates/text_x-email */ +{ + { /* default frame */ + kAttrWindowFrame, + B_RECT_TYPE, + 16, + (const char *)&kDefaultFrame + }, + { /* attr: _trk/viewstate */ + kAttrViewState_be, + B_RAW_TYPE, + 49, + "o^\365R\000\000\000\012Tlst\000\000\000\000\000\000\000\000\000\000" + "\000\000\000\000\000\000\000\000\000\000\366_\377ETIME\000\000\000" + "\000\000\000\000\000\000" + }, + { /* attr: _trk/columns */ + kAttrColumns_be, + B_RAW_TYPE, + 222, + "O\362VR\000\000\000\025\000\000\000\007Subject\000B \000\000B\334\000" + "\000\000\000\000\000\000\000\000\014MAIL:subject\000\343\173\337RC" + "STR\000\000O\362VR\000\000\000\025\000\000\000\004From\000C%\000\000" + "C\031\000\000\000\000\000\000\000\000\000\011MAIL:from\000\317s_RC" + "STR\000\000O\362VR\000\000\000\025\000\000\000\004When\000C\246\200" + "\000B\360\000\000\000\000\000\000\000\000\000\011MAIL:when\000\366" + "_\377ETIME\000\000O\362VR\000\000\000\025\000\000\000\006Status\000" + "C\352\000\000BH\000\000\000\000\000\001\000\000\000\013MAIL:status" + "\000G\363\134RCSTR\000\001" + }, +}; + diff --git a/src/kits/tracker/TrackerScripting.cpp b/src/kits/tracker/TrackerScripting.cpp new file mode 100644 index 0000000000..28f30e010c --- /dev/null +++ b/src/kits/tracker/TrackerScripting.cpp @@ -0,0 +1,302 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include +#include + +#include "Tracker.h" +#include "FSUtils.h" + +#define kPropertyTrash "Trash" +#define kPropertyFolder "Folder" + +#if 0 + +doo Tracker delete Trash +doo Tracker create Folder to '/boot/home/Desktop/hello' + +ToDo: +Create file: on a "Tracker" "File" "B_CREATE_PROPERTY" "name" +Create query: on a "Tracker" "Query" "B_CREATE_PROPERTY" "name" +Open a folder: Tracker Execute "Folder" bla +Find a window for a path + +#endif + + +#if _SUPPORTS_FEATURE_SCRIPTING + +const property_info kTrackerPropertyList[] = { + { kPropertyTrash, + { B_DELETE_PROPERTY }, + { B_DIRECT_SPECIFIER }, + "delete Trash # Empties the Trash", + 0, + {}, + {}, + {} + }, + { kPropertyFolder, + { B_CREATE_PROPERTY }, + { B_DIRECT_SPECIFIER }, + "create Folder to path # creates a new folder", + 0, + { B_REF_TYPE }, + {}, + {} + }, + {NULL, + {}, + {}, + NULL, 0, + {}, + {}, + {} + } +}; + + +status_t +TTracker::GetSupportedSuites(BMessage *data) +{ + data->AddString("suites", kTrackerSuites); + BPropertyInfo propertyInfo(const_cast(kTrackerPropertyList)); + data->AddFlat("messages", &propertyInfo); + + return _inherited::GetSupportedSuites(data); +} + + +BHandler * +TTracker::ResolveSpecifier(BMessage *message, int32 index, + BMessage *specifier, int32 form, const char *property) +{ + BPropertyInfo propertyInfo(const_cast(kTrackerPropertyList)); + + int32 result = propertyInfo.FindMatch(message, index, specifier, form, property); + if (result < 0) { + //PRINT(("FindMatch result %d %s\n", result, strerror(result))); + return _inherited::ResolveSpecifier(message, index, specifier, + form, property); + } + + return this; +} + + +bool +TTracker::HandleScriptingMessage(BMessage *message) +{ + if (message->what != B_GET_PROPERTY + && message->what != B_SET_PROPERTY + && message->what != B_CREATE_PROPERTY + && message->what != B_COUNT_PROPERTIES + && message->what != B_DELETE_PROPERTY + && message->what != B_EXECUTE_PROPERTY) + return false; + + // dispatch scripting messages + BMessage reply(B_REPLY); + const char *property = 0; + bool handled = false; + + int32 index = 0; + int32 form = 0; + BMessage specifier; + + status_t result = message->GetCurrentSpecifier(&index, &specifier, + &form, &property); + + if (result != B_OK || index == -1) + return false; + + ASSERT(property); + + switch (message->what) { + case B_CREATE_PROPERTY: + handled = CreateProperty(message, &specifier, form, property, &reply); + break; + + case B_GET_PROPERTY: + handled = GetProperty(&specifier, form, property, &reply); + break; + + case B_SET_PROPERTY: + handled = SetProperty(message, &specifier, form, property, &reply); + break; + + case B_COUNT_PROPERTIES: + handled = CountProperty(&specifier, form, property, &reply); + break; + + case B_DELETE_PROPERTY: + handled = DeleteProperty(&specifier, form, property, &reply); + break; + + case B_EXECUTE_PROPERTY: + handled = ExecuteProperty(&specifier, form, property, &reply); + break; + } + + if (handled) + // done handling message, send a reply + message->SendReply(&reply); + + return handled; +} + + +bool +TTracker::CreateProperty(BMessage *message, BMessage *, int32 form, + const char *property, BMessage *reply) +{ + bool handled = false; + status_t error = B_OK; + if (strcmp(property, kPropertyFolder) == 0) { + if (form != B_DIRECT_SPECIFIER) + return false; + + // create new empty folders + entry_ref ref; + for (int32 index = 0; + message->FindRef("data", index, &ref) == B_OK; index++) { + + BEntry entry(&ref); + if (!entry.Exists()) + error = FSCreateNewFolder(&ref); + + if (error != B_OK) + break; + } + + handled = true; + } + + if (error != B_OK) + reply->AddInt32("error", error); + + return handled; +} + + +bool +TTracker::DeleteProperty(BMessage */*specifier*/, int32 form, + const char *property, BMessage */*reply*/) +{ + if (strcmp(property, kPropertyTrash) == 0) { + // deleting on a selection is handled as removing a part of the selection + // not to be confused with deleting a selected item + + if (form != B_DIRECT_SPECIFIER) + // only support direct specifier + return false; + + // empty the trash + FSEmptyTrash(); + return true; + + } + return false; +} + +#else /* _SUPPORTS_FEATURE_SCRIPTING */ + +status_t +TTracker::GetSupportedSuites(BMessage */*data*/) +{ + return B_UNSUPPORTED; +} + + +BHandler * +TTracker::ResolveSpecifier(BMessage */*message*/, + int32 /*index*/, BMessage */*specifier*/, + int32 /*form*/, const char */*property*/) +{ + return NULL; +} + + +bool +TTracker::HandleScriptingMessage(BMessage */*message*/) +{ + return false; +} + + +bool +TTracker::CreateProperty(BMessage */*message*/, BMessage *, int32 /*form*/, + const char */*property*/, BMessage */*reply*/) +{ + return false; +} + + +bool +TTracker::DeleteProperty(BMessage */*specifier*/, int32 /*form*/, + const char */*property*/, BMessage *) +{ + return false; +} + +#endif /* _SUPPORTS_FEATURE_SCRIPTING */ + + +bool +TTracker::ExecuteProperty(BMessage *, int32 , const char *, BMessage *) +{ + return false; +} + + +bool +TTracker::CountProperty(BMessage *, int32, const char *, BMessage *) +{ + return false; +} + + +bool +TTracker::GetProperty(BMessage *, int32, const char *, BMessage *) +{ + return false; +} + + +bool +TTracker::SetProperty(BMessage *, BMessage *, int32, const char *, BMessage *) +{ + return false; +} + diff --git a/src/kits/tracker/TrackerSettings.cpp b/src/kits/tracker/TrackerSettings.cpp new file mode 100644 index 0000000000..dc1ed73ced --- /dev/null +++ b/src/kits/tracker/TrackerSettings.cpp @@ -0,0 +1,582 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + + +#include "Tracker.h" +#include "TrackerSettings.h" +#include "WidgetAttributeText.h" + + +class TTrackerState : public Settings { + public: + static TTrackerState *Get(); + void Release(); + + void LoadSettingsIfNeeded(); + void SaveSettings(bool onlyIfNonDefault = true); + + TTrackerState(); + ~TTrackerState(); + + private: + friend TrackerSettings; + + static void InitIfNeeded(); + TTrackerState(const TTrackerState&); + + BooleanValueSetting *fShowDisksIcon; + BooleanValueSetting *fMountVolumesOntoDesktop; + BooleanValueSetting *fIntegrateNonBootBeOSDesktops; + BooleanValueSetting *fIntegrateAllNonBootDesktops; + BooleanValueSetting *fDesktopFilePanelRoot; + BooleanValueSetting *fMountSharedVolumesOntoDesktop; + BooleanValueSetting *fEjectWhenUnmounting; + + BooleanValueSetting *fShowFullPathInTitleBar; + BooleanValueSetting *fSingleWindowBrowse; + BooleanValueSetting *fShowNavigator; + BooleanValueSetting *fShowSelectionWhenInactive; + BooleanValueSetting *fTransparentSelection; + BooleanValueSetting *fSortFolderNamesFirst; + BooleanValueSetting *fHideDotFiles; + + BooleanValueSetting *f24HrClock; + + ScalarValueSetting *fRecentApplicationsCount; + ScalarValueSetting *fRecentDocumentsCount; + ScalarValueSetting *fRecentFoldersCount; + ScalarValueSetting *fTimeFormatSeparator; + ScalarValueSetting *fDateOrderFormat; + + BooleanValueSetting *fShowVolumeSpaceBar; + HexScalarValueSetting *fUsedSpaceColor; + HexScalarValueSetting *fFreeSpaceColor; + HexScalarValueSetting *fWarningSpaceColor; + + BooleanValueSetting *fDontMoveFilesToTrash; + BooleanValueSetting *fAskBeforeDeleteFile; + + Benaphore fInitLock; + bool fInited; + bool fSettingsLoaded; + + int32 fUseCounter; + + typedef Settings _inherited; +}; + +static TTrackerState gTrackerState; + + +rgb_color ValueToColor(int32 value) +{ + rgb_color color; + color.alpha = static_cast((value >> 24L) & 0xff); + color.red = static_cast((value >> 16L) & 0xff); + color.green = static_cast((value >> 8L) & 0xff); + color.blue = static_cast(value & 0xff); + + // zero alpha is invalid + if (color.alpha == 0) + color.alpha = 192; + + return color; +} + +int32 ColorToValue(rgb_color color) +{ + // zero alpha is invalid + if (color.alpha == 0) + color.alpha = 192; + + return color.alpha << 24L + | color.red << 16L + | color.green << 8L + | color.blue; +} + + +// #pragma mark - + + +TTrackerState::TTrackerState() + : Settings("TrackerSettings", "Tracker"), + fInited(false), + fSettingsLoaded(false) +{ +} + + +TTrackerState::TTrackerState(const TTrackerState&) + : Settings("", "") +{ + // Placeholder copy constructor to prevent others from accidentally using the + // default copy constructor. Note, the DEBUGGER call is for the off chance that + // a TTrackerState method (or friend) tries to make a copy. + DEBUGGER("Don't make a copy of this!"); +} + + +TTrackerState::~TTrackerState() +{ +} + + +void +TTrackerState::SaveSettings(bool onlyIfNonDefault) +{ + if (fSettingsLoaded) + _inherited::SaveSettings(onlyIfNonDefault); +} + + +void +TTrackerState::LoadSettingsIfNeeded() +{ + if (fSettingsLoaded) + return; + + Add(fShowDisksIcon = new BooleanValueSetting("ShowDisksIcon", false)); + Add(fMountVolumesOntoDesktop = new BooleanValueSetting("MountVolumesOntoDesktop", true)); + Add(fMountSharedVolumesOntoDesktop = + new BooleanValueSetting("MountSharedVolumesOntoDesktop", false)); + Add(fIntegrateNonBootBeOSDesktops = new BooleanValueSetting + ("IntegrateNonBootBeOSDesktops", true)); + Add(fIntegrateAllNonBootDesktops = new BooleanValueSetting + ("IntegrateAllNonBootDesktops", false)); + Add(fEjectWhenUnmounting = new BooleanValueSetting("EjectWhenUnmounting", true)); + + Add(fDesktopFilePanelRoot = new BooleanValueSetting("DesktopFilePanelRoot", true)); + Add(fShowFullPathInTitleBar = new BooleanValueSetting("ShowFullPathInTitleBar", false)); + Add(fShowSelectionWhenInactive = new BooleanValueSetting("ShowSelectionWhenInactive", true)); + Add(fTransparentSelection = new BooleanValueSetting("TransparentSelection", false)); + Add(fSortFolderNamesFirst = new BooleanValueSetting("SortFolderNamesFirst", false)); + Add(fHideDotFiles = new BooleanValueSetting("HideDotFiles", false)); + Add(fSingleWindowBrowse = new BooleanValueSetting("SingleWindowBrowse", false)); + Add(fShowNavigator = new BooleanValueSetting("ShowNavigator", false)); + + Add(fRecentApplicationsCount = new ScalarValueSetting("RecentApplications", 10, "", "")); + Add(fRecentDocumentsCount = new ScalarValueSetting("RecentDocuments", 10, "", "")); + Add(fRecentFoldersCount = new ScalarValueSetting("RecentFolders", 10, "", "")); + + Add(fTimeFormatSeparator = new ScalarValueSetting("TimeFormatSeparator", 3, "", "")); + Add(fDateOrderFormat = new ScalarValueSetting("DateOrderFormat", 2, "", "")); + Add(f24HrClock = new BooleanValueSetting("24HrClock", false)); + + Add(fShowVolumeSpaceBar = new BooleanValueSetting("ShowVolumeSpaceBar", false)); + + Add(fUsedSpaceColor = new HexScalarValueSetting("UsedSpaceColor", 0xc000cb00, "", "")); + Add(fFreeSpaceColor = new HexScalarValueSetting("FreeSpaceColor", 0xc0ffffff, "", "")); + Add(fWarningSpaceColor = new HexScalarValueSetting("WarningSpaceColor", 0xc0cb0000, "", "")); + + Add(fDontMoveFilesToTrash = new BooleanValueSetting("DontMoveFilesToTrash", false)); + Add(fAskBeforeDeleteFile = new BooleanValueSetting("AskBeforeDeleteFile", true)); + + TryReadingSettings(); + + NameAttributeText::SetSortFolderNamesFirst(fSortFolderNamesFirst->Value()); + + fSettingsLoaded = true; +} + + +// #pragma mark - + + +TrackerSettings::TrackerSettings() +{ + gTrackerState.LoadSettingsIfNeeded(); +} + + +void +TrackerSettings::SaveSettings(bool onlyIfNonDefault) +{ + gTrackerState.SaveSettings(onlyIfNonDefault); +} + + +bool +TrackerSettings::ShowDisksIcon() +{ + return gTrackerState.fShowDisksIcon->Value(); +} + + +void +TrackerSettings::SetShowDisksIcon(bool enabled) +{ + gTrackerState.fShowDisksIcon->SetValue(enabled); +} + + +bool +TrackerSettings::DesktopFilePanelRoot() +{ + return gTrackerState.fDesktopFilePanelRoot->Value(); +} + + +void +TrackerSettings::SetDesktopFilePanelRoot(bool enabled) +{ + gTrackerState.fDesktopFilePanelRoot->SetValue(enabled); +} + + +bool +TrackerSettings::MountVolumesOntoDesktop() +{ + return gTrackerState.fMountVolumesOntoDesktop->Value(); +} + + +void +TrackerSettings::SetMountVolumesOntoDesktop(bool enabled) +{ + gTrackerState.fMountVolumesOntoDesktop->SetValue(enabled); +} + + +bool +TrackerSettings::MountSharedVolumesOntoDesktop() +{ + return gTrackerState.fMountSharedVolumesOntoDesktop->Value(); +} + + +void +TrackerSettings::SetMountSharedVolumesOntoDesktop(bool enabled) +{ + gTrackerState.fMountSharedVolumesOntoDesktop->SetValue(enabled); +} + + +bool +TrackerSettings::IntegrateNonBootBeOSDesktops() +{ + return gTrackerState.fIntegrateNonBootBeOSDesktops->Value(); +} + + +void +TrackerSettings::SetIntegrateNonBootBeOSDesktops(bool enabled) +{ + gTrackerState.fIntegrateNonBootBeOSDesktops->SetValue(enabled); +} + + +bool +TrackerSettings::IntegrateAllNonBootDesktops() +{ + return gTrackerState.fIntegrateAllNonBootDesktops->Value(); +} + +bool +TrackerSettings::EjectWhenUnmounting() +{ + return gTrackerState.fEjectWhenUnmounting->Value(); +} + + +void +TrackerSettings::SetEjectWhenUnmounting(bool enabled) +{ + gTrackerState.fEjectWhenUnmounting->SetValue(enabled); +} + + +bool +TrackerSettings::ShowVolumeSpaceBar() +{ + return gTrackerState.fShowVolumeSpaceBar->Value(); +} + + +void +TrackerSettings::SetShowVolumeSpaceBar(bool enabled) +{ + gTrackerState.fShowVolumeSpaceBar->SetValue(enabled); +} + + +rgb_color +TrackerSettings::UsedSpaceColor() +{ + return ValueToColor(gTrackerState.fUsedSpaceColor->Value()); +} + + +void +TrackerSettings::SetUsedSpaceColor(rgb_color color) +{ + if (color.alpha == 0) + color.alpha = 192; + gTrackerState.fUsedSpaceColor->ValueChanged(ColorToValue(color)); +} + + +rgb_color +TrackerSettings::FreeSpaceColor() +{ + return ValueToColor(gTrackerState.fFreeSpaceColor->Value()); +} + + +void +TrackerSettings::SetFreeSpaceColor(rgb_color color) +{ + if (color.alpha == 0) + color.alpha = 192; + gTrackerState.fFreeSpaceColor->ValueChanged(ColorToValue(color)); +} + + +rgb_color +TrackerSettings::WarningSpaceColor() +{ + return ValueToColor(gTrackerState.fWarningSpaceColor->Value()); +} + + +void +TrackerSettings::SetWarningSpaceColor(rgb_color color) +{ + if (color.alpha == 0) + color.alpha = 192; + gTrackerState.fWarningSpaceColor->ValueChanged(ColorToValue(color)); +} + + +bool +TrackerSettings::ShowFullPathInTitleBar() +{ + return gTrackerState.fShowFullPathInTitleBar->Value(); +} + + +void +TrackerSettings::SetShowFullPathInTitleBar(bool enabled) +{ + gTrackerState.fShowFullPathInTitleBar->SetValue(enabled); +} + + +bool +TrackerSettings::SortFolderNamesFirst() +{ + return gTrackerState.fSortFolderNamesFirst->Value(); +} + + +void +TrackerSettings::SetSortFolderNamesFirst(bool enabled) +{ + gTrackerState.fSortFolderNamesFirst->SetValue(enabled); + NameAttributeText::SetSortFolderNamesFirst(enabled); +} + + +bool +TrackerSettings::HideDotFiles() +{ + return gTrackerState.fHideDotFiles->Value(); +} + + +void +TrackerSettings::SetHideDotFiles(bool hide) +{ + gTrackerState.fHideDotFiles->SetValue(hide); +} + + +bool +TrackerSettings::ShowSelectionWhenInactive() +{ + return gTrackerState.fShowSelectionWhenInactive->Value(); +} + + +void +TrackerSettings::SetShowSelectionWhenInactive(bool enabled) +{ + gTrackerState.fShowSelectionWhenInactive->SetValue(enabled); +} + + +bool +TrackerSettings::TransparentSelection() +{ + return gTrackerState.fTransparentSelection->Value(); +} + + +void +TrackerSettings::SetTransparentSelection(bool enabled) +{ + gTrackerState.fTransparentSelection->SetValue(enabled); +} + + +bool +TrackerSettings::SingleWindowBrowse() +{ + return gTrackerState.fSingleWindowBrowse->Value(); +} + + +void +TrackerSettings::SetSingleWindowBrowse(bool enabled) +{ + gTrackerState.fSingleWindowBrowse->SetValue(enabled); +} + + +bool +TrackerSettings::ShowNavigator() +{ + return gTrackerState.fShowNavigator->Value(); +} + + +void +TrackerSettings::SetShowNavigator(bool enabled) +{ + gTrackerState.fShowNavigator->SetValue(enabled); +} + + +void +TrackerSettings::RecentCounts(int32 *applications, int32 *documents, int32 *folders) +{ + if (applications) + *applications = gTrackerState.fRecentApplicationsCount->Value(); + if (documents) + *documents = gTrackerState.fRecentDocumentsCount->Value(); + if (folders) + *folders = gTrackerState.fRecentFoldersCount->Value(); +} + + +void +TrackerSettings::SetRecentApplicationsCount(int32 count) +{ + gTrackerState.fRecentApplicationsCount->ValueChanged(count); +} + + +void +TrackerSettings::SetRecentDocumentsCount(int32 count) +{ + gTrackerState.fRecentDocumentsCount->ValueChanged(count); +} + + +void +TrackerSettings::SetRecentFoldersCount(int32 count) +{ + gTrackerState.fRecentFoldersCount->ValueChanged(count); +} + + +FormatSeparator +TrackerSettings::TimeFormatSeparator() +{ + return (FormatSeparator)gTrackerState.fTimeFormatSeparator->Value(); +} + + +void +TrackerSettings::SetTimeFormatSeparator(FormatSeparator separator) +{ + gTrackerState.fTimeFormatSeparator->ValueChanged((int32)separator); +} + + +DateOrder +TrackerSettings::DateOrderFormat() +{ + return (DateOrder)gTrackerState.fDateOrderFormat->Value(); +} + + +void +TrackerSettings::SetDateOrderFormat(DateOrder order) +{ + gTrackerState.fDateOrderFormat->ValueChanged((int32)order); +} + + +bool +TrackerSettings::ClockIs24Hr() +{ + return gTrackerState.f24HrClock->Value(); +} + + +void +TrackerSettings::SetClockTo24Hr(bool enabled) +{ + gTrackerState.f24HrClock->SetValue(enabled); +} + + +bool +TrackerSettings::DontMoveFilesToTrash() +{ + return gTrackerState.fDontMoveFilesToTrash->Value(); +} + + +void +TrackerSettings::SetDontMoveFilesToTrash(bool enabled) +{ + gTrackerState.fDontMoveFilesToTrash->SetValue(enabled); +} + + +bool +TrackerSettings::AskBeforeDeleteFile() +{ + return gTrackerState.fAskBeforeDeleteFile->Value(); +} + + +void +TrackerSettings::SetAskBeforeDeleteFile(bool enabled) +{ + gTrackerState.fAskBeforeDeleteFile->SetValue(enabled); +} + diff --git a/src/kits/tracker/TrackerSettings.h b/src/kits/tracker/TrackerSettings.h new file mode 100644 index 0000000000..4b144a064b --- /dev/null +++ b/src/kits/tracker/TrackerSettings.h @@ -0,0 +1,135 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef _TRACKER_SETTINGS_H +#define _TRACKER_SETTINGS_H + + +#include "Utilities.h" +#include "Settings.h" + + +namespace BPrivate { + +enum FormatSeparator { + kNoSeparator, + kSpaceSeparator, + kMinusSeparator, + kSlashSeparator, + kBackslashSeparator, + kDotSeparator, + kSeparatorsEnd +}; + +enum DateOrder { + kYMDFormat, + kDMYFormat, + kMDYFormat, + kDateFormatEnd +}; + + + +class TrackerSettings { + public: + TrackerSettings(); + + //TTrackerState *Settings() const { return fSettings; } + void SaveSettings(bool onlyIfNonDefault = true); + + bool ShowDisksIcon(); + void SetShowDisksIcon(bool); + bool DesktopFilePanelRoot(); + void SetDesktopFilePanelRoot(bool); + bool MountVolumesOntoDesktop(); + void SetMountVolumesOntoDesktop(bool); + bool MountSharedVolumesOntoDesktop(); + void SetMountSharedVolumesOntoDesktop(bool); + bool IntegrateNonBootBeOSDesktops(); + void SetIntegrateNonBootBeOSDesktops(bool); + bool IntegrateAllNonBootDesktops(); + void SetIntegrateAllNonBootDesktops(bool); + bool EjectWhenUnmounting(); + void SetEjectWhenUnmounting(bool); + + bool ShowVolumeSpaceBar(); + void SetShowVolumeSpaceBar(bool); + rgb_color UsedSpaceColor(); + void SetUsedSpaceColor(rgb_color color); + rgb_color FreeSpaceColor(); + void SetFreeSpaceColor(rgb_color color); + rgb_color WarningSpaceColor(); + void SetWarningSpaceColor(rgb_color color); + + bool ShowFullPathInTitleBar(); + void SetShowFullPathInTitleBar(bool); + bool SortFolderNamesFirst(); + void SetSortFolderNamesFirst(bool); + bool HideDotFiles(); + void SetHideDotFiles(bool hide); + + bool ShowSelectionWhenInactive(); + void SetShowSelectionWhenInactive(bool); + bool TransparentSelection(); + void SetTransparentSelection(bool); + + bool SingleWindowBrowse(); + void SetSingleWindowBrowse(bool); + bool ShowNavigator(); + void SetShowNavigator(bool); + + void RecentCounts(int32 *applications, int32 *documents, int32 *folders); + void SetRecentApplicationsCount(int32); + void SetRecentDocumentsCount(int32); + void SetRecentFoldersCount(int32); + + FormatSeparator TimeFormatSeparator(); + void SetTimeFormatSeparator(FormatSeparator); + DateOrder DateOrderFormat(); + void SetDateOrderFormat(DateOrder); + bool ClockIs24Hr(); + void SetClockTo24Hr(bool); + + bool DontMoveFilesToTrash(); + void SetDontMoveFilesToTrash(bool); + bool AskBeforeDeleteFile(); + void SetAskBeforeDeleteFile(bool); + + private: + //TTrackerState *fSettings; +}; + +} // namespace BPrivate + +#endif /* _TRACKER_SETTINGS_H */ diff --git a/src/kits/tracker/TrackerSettingsWindow.cpp b/src/kits/tracker/TrackerSettingsWindow.cpp new file mode 100644 index 0000000000..c7e399f147 --- /dev/null +++ b/src/kits/tracker/TrackerSettingsWindow.cpp @@ -0,0 +1,326 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include "SettingsViews.h" +#include "TrackerSettings.h" +#include "TrackerSettingsWindow.h" + +#include + +const BPoint kSettingsWindowOffset(30, 30); +const float kSettingsWindowsWidth = 370; +const float kSettingsWindowsHeight = 270; + +const uint32 kSettingsViewChanged = 'Svch'; +const uint32 kDefaultsButtonPressed = 'Apbp'; +const uint32 kRevertButtonPressed = 'Rebp'; + + +TrackerSettingsWindow::TrackerSettingsWindow() + : BWindow(BRect(kSettingsWindowOffset.x, kSettingsWindowOffset.y, + kSettingsWindowOffset.x + kSettingsWindowsWidth, + kSettingsWindowOffset.y + kSettingsWindowsHeight), + "Tracker Settings", B_TITLED_WINDOW, B_NOT_MINIMIZABLE | B_NOT_RESIZABLE + | B_NO_WORKSPACE_ACTIVATION | B_NOT_ANCHORED_ON_ACTIVATE + | B_ASYNCHRONOUS_CONTROLS | B_NOT_ZOOMABLE) +{ + BView *backgroundView = new BView(Bounds(), "Background", B_FOLLOW_ALL_SIDES, 0); + + backgroundView->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + AddChild(backgroundView); + + const float kBorderDistance = 12; + const float kListViewWidth = 90; + const float kListViewHeight = kSettingsWindowsHeight - 2 * kBorderDistance; + const float kBoxWidth = kSettingsWindowsWidth - kListViewWidth - 3 * (kBorderDistance - 1); + const float kBoxHeight = kListViewHeight - 30; + + BRect listViewRect(kBorderDistance, kBorderDistance, kBorderDistance + kListViewWidth, + kBorderDistance + kListViewHeight); + + BBox *borderBox = new BBox(listViewRect.InsetByCopy(-2, -2)); + + backgroundView->AddChild(borderBox); + + listViewRect.OffsetTo(2, 2); + listViewRect.right -= 1; + + fSettingsTypeListView = new BListView(listViewRect, "List View"); + + borderBox->AddChild(fSettingsTypeListView); + + fSettingsContainerBox = new BBox(BRect(kBorderDistance + kListViewWidth + kBorderDistance, + kBorderDistance, kBorderDistance + kListViewWidth + kBorderDistance + kBoxWidth, + kBorderDistance + kBoxHeight)); + + backgroundView->AddChild(fSettingsContainerBox); + + const float kButtonTop = fSettingsContainerBox->Frame().bottom + kBorderDistance; + const float kDefaultsButtonLeft = fSettingsContainerBox->Frame().left; + const float kButtonWidth = 45; + const float kButtonHeight = 20; + + fDefaultsButton = new BButton(BRect(kDefaultsButtonLeft, kButtonTop, + kDefaultsButtonLeft + kButtonWidth, kButtonTop + kButtonHeight), + "Defaults", "Defaults", new BMessage(kDefaultsButtonPressed)); + + backgroundView->AddChild(fDefaultsButton); + + fDefaultsButton->ResizeToPreferred(); + fDefaultsButton->SetEnabled(true); + + fRevertButton = new BButton(BRect(fDefaultsButton->Frame().right + kBorderDistance, + kButtonTop, fDefaultsButton->Frame().right + kBorderDistance + kButtonWidth, kButtonTop + + kButtonHeight), "Revert", "Revert", new BMessage(kRevertButtonPressed)); + + fRevertButton->SetEnabled(false); + fRevertButton->ResizeToPreferred(); + backgroundView->AddChild(fRevertButton); + + BRect SettingsViewSize = fSettingsContainerBox->Bounds().InsetByCopy(5, 5); + + SettingsViewSize.top += 10; + + fSettingsTypeListView->AddItem(new SettingsItem("Desktop", + new DesktopSettingsView(SettingsViewSize))); + fSettingsTypeListView->AddItem(new SettingsItem("Windows", + new WindowsSettingsView(SettingsViewSize))); + fSettingsTypeListView->AddItem(new SettingsItem("File Panel", + new FilePanelSettingsView(SettingsViewSize))); + fSettingsTypeListView->AddItem(new SettingsItem("Time Format", + new TimeFormatSettingsView(SettingsViewSize))); + fSettingsTypeListView->AddItem(new SettingsItem("Trash", + new TrashSettingsView(SettingsViewSize))); + fSettingsTypeListView->AddItem(new SettingsItem("Volume Icons", + new SpaceBarSettingsView(SettingsViewSize))); + + fSettingsTypeListView->SetSelectionMessage(new BMessage(kSettingsViewChanged)); + + fSettingsTypeListView->Select(0); +} + + +bool +TrackerSettingsWindow::QuitRequested() +{ + bool isHidden = false; + + if (Lock()) { + isHidden = IsHidden(); + Unlock(); + } else + return true; + + if (isHidden) + return true; + + Hide(); + + return false; +} + + +void +TrackerSettingsWindow::MessageReceived(BMessage *message) +{ + + switch (message->what) { + case kSettingsContentsModified: + HandleChangedContents(); + break; + + case kDefaultsButtonPressed: + HandlePressedDefaultsButton(); + break; + + case kRevertButtonPressed: + HandlePressedRevertButton(); + break; + + case kSettingsViewChanged: + HandleChangedSettingsView(); + break; + + default: + _inherited::MessageReceived(message); + } +} + + +void +TrackerSettingsWindow::Show() +{ + if (Lock()) { + + int32 itemCount = fSettingsTypeListView->CountItems(); + + for (int32 i = 0; iRecordRevertSettings(); + ViewAt(i)->ShowCurrentSettings(); + } + + fSettingsTypeListView->Invalidate(); + + Unlock(); + } + _inherited::Show(); +} + + +SettingsView * +TrackerSettingsWindow::ViewAt(int32 i) +{ + if (!Lock()) + return NULL; + + SettingsItem *item = dynamic_cast(fSettingsTypeListView->ItemAt(i)); + + Unlock(); + + return item->View(); +} + + +void +TrackerSettingsWindow::HandleChangedContents() +{ + int32 itemCount = fSettingsTypeListView->CountItems(); + + bool revertable = false; + + for (int32 i = 0; i < itemCount; i++) + revertable |= ! ViewAt(i)->ShowsRevertSettings(); + + fSettingsTypeListView->Invalidate(); + fRevertButton->SetEnabled(revertable); + + TrackerSettings().SaveSettings(false); +} + + +void +TrackerSettingsWindow::HandlePressedDefaultsButton() +{ + int32 itemCount = fSettingsTypeListView->CountItems(); + + for (int32 i = 0; i < itemCount; i++) + ViewAt(i)->SetDefaults(); + + HandleChangedContents(); +} + + +void +TrackerSettingsWindow::HandlePressedRevertButton() +{ + int32 itemCount = fSettingsTypeListView->CountItems(); + + for (int32 i = 0; i < itemCount; i++) + if (ViewAt(i)->ShowsRevertSettings() == false) + ViewAt(i)->Revert(); + + HandleChangedContents(); +} + +void +TrackerSettingsWindow::HandleChangedSettingsView() +{ + int32 currentSelection = fSettingsTypeListView->CurrentSelection(); + + if (currentSelection < 0) + return; + + BView *oldView = fSettingsContainerBox->ChildAt(0); + + if (oldView) + oldView->RemoveSelf(); + + SettingsItem *selectedItem = + dynamic_cast(fSettingsTypeListView->ItemAt(currentSelection)); + + if (selectedItem) { + fSettingsContainerBox->SetLabel(selectedItem->Text()); + selectedItem->View()->SetViewColor(fSettingsContainerBox->ViewColor()); + fSettingsContainerBox->AddChild(selectedItem->View()); + } +} + +SettingsItem::SettingsItem(const char *label, SettingsView *view) + : BStringItem(label), + fSettingsView(view) +{ +} + +void +SettingsItem::DrawItem(BView *owner, BRect rect, bool drawEverything) +{ + const rgb_color kModifiedColor = {0, 0, 255, 0}; + const rgb_color kBlack = {0, 0, 0, 0}; + const rgb_color kSelectedColor = {140, 140, 140, 0}; + + if (fSettingsView) { + bool showsRevertSettings = fSettingsView->ShowsRevertSettings(); + bool isSelected = IsSelected(); + + if (isSelected || drawEverything) { + rgb_color color; + if (isSelected) + color = kSelectedColor; + else + color = owner->ViewColor(); + + owner->SetHighColor(color); + owner->SetLowColor(color); + owner->FillRect(rect); + } + + if (!showsRevertSettings) + owner->SetHighColor(kModifiedColor); + else + owner->SetHighColor(kBlack); + + owner->MovePenTo(rect.left + 4, rect.bottom - 2); + + owner->DrawString(Text()); + + owner->SetHighColor(kBlack); + owner->SetLowColor(owner->ViewColor()); + } +} + +SettingsView * +SettingsItem::View() +{ + return fSettingsView; +} diff --git a/src/kits/tracker/TrackerSettingsWindow.h b/src/kits/tracker/TrackerSettingsWindow.h new file mode 100644 index 0000000000..61dfe0fdc7 --- /dev/null +++ b/src/kits/tracker/TrackerSettingsWindow.h @@ -0,0 +1,88 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef _TRACKER_SETTINGS_WINDOW +#define _TRACKER_SETTINGS_WINDOW + +#include +#include +#include +#include +#include + +#include "SettingsViews.h" + +namespace BPrivate { + +class TrackerSettingsWindow : public BWindow { +public: + TrackerSettingsWindow(); + + bool QuitRequested(); + void MessageReceived(BMessage *); + void Show(); + + SettingsView *ViewAt(int32 i); + + void HandleChangedContents(); + void HandlePressedDefaultsButton(); + void HandlePressedRevertButton(); + void HandleChangedSettingsView(); + +private: + BListView *fSettingsTypeListView; + BBox *fSettingsContainerBox; + BButton *fDefaultsButton; + BButton *fRevertButton; + + typedef BWindow _inherited; +}; + + +class SettingsItem : public BStringItem { +public: + SettingsItem(const char *, SettingsView *); + + void DrawItem(BView *owner, BRect rect, bool drawEverything); + + SettingsView *View(); +private: + SettingsView *fSettingsView; +}; + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/TrackerString.cpp b/src/kits/tracker/TrackerString.cpp new file mode 100644 index 0000000000..1393fa2f84 --- /dev/null +++ b/src/kits/tracker/TrackerString.cpp @@ -0,0 +1,684 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include "TrackerString.h" + +#include +#include + +TrackerString::TrackerString() +{ +} + + +TrackerString::TrackerString(const char *string) + : BString(string) +{ +} + + +TrackerString::TrackerString(const TrackerString &string) + : BString(string) +{ +} + + +TrackerString::TrackerString(const char *string, int32 maxLength) + : BString(string, maxLength) +{ +} + + +TrackerString::~TrackerString() +{ +} + + +bool +TrackerString::Matches(const char *string, bool caseSensitivity, + TrackerStringExpressionType expressionType) const +{ + switch (expressionType) { + default: + case kNone: + return false; + + case kStartsWith: + return StartsWith(string, caseSensitivity); + + case kEndsWith: + return EndsWith(string, caseSensitivity); + + case kContains: + return Contains(string, caseSensitivity); + + case kGlobMatch: + return MatchesGlob(string, caseSensitivity); + + case kRegexpMatch: + return MatchesRegExp(string, caseSensitivity); + } +} + + +bool +TrackerString::MatchesRegExp(const char *pattern, bool caseSensitivity) const +{ + BString patternString(pattern); + BString textString(String()); + + if (caseSensitivity == false) { + patternString.ToLower(); + textString.ToLower(); + } + + RegExp expression(patternString); + + if (expression.InitCheck() != B_OK) + return false; + + return expression.Matches(textString); +} + + +bool +TrackerString::MatchesGlob(const char *string, bool caseSensitivity) const +{ + return StringMatchesPattern(String(), string, caseSensitivity); +} + + +bool +TrackerString::EndsWith(const char *string, bool caseSensitivity) const +{ + // If "string" is longer than "this", + // we should simply return false + int32 position = Length() - (int32)strlen(string); + if (position < 0) + return false; + + if (caseSensitivity) + return FindLast(string) == position; + else + return IFindLast(string) == position; +} + + +bool +TrackerString::StartsWith(const char *string, bool caseSensitivity) const +{ + if (caseSensitivity) + return FindFirst(string) == 0; + else + return IFindFirst(string) == 0; +} + + +bool +TrackerString::Contains(const char *string, bool caseSensitivity) const +{ + if (caseSensitivity) + return FindFirst(string) > -1; + else + return IFindFirst(string) > -1; +} + + +// About the ?Find* functions: +// The leading star here has been compliance with BString, +// simplicity and performance. Therefore unncessary copying +// has been avoided, as unncessary function calls. +// The copying has been avoided by implementing the +// ?Find*(const char*) functions rather than +// the ?Find*(TrackerString &) functions. +// The function calls has been avoided by +// inserting a check on the first character +// before calling the str*cmp functions. + + +int32 +TrackerString::FindFirst(const BString &string) const +{ + return FindFirst(string.String(), 0); +} + + +int32 +TrackerString::FindFirst(const char *string) const +{ + return FindFirst(string, 0); +} + + +int32 +TrackerString::FindFirst(const BString &string, int32 fromOffset) const +{ + return FindFirst(string.String(), fromOffset); +} + + +int32 +TrackerString::FindFirst(const char *string, int32 fromOffset) const +{ + if (!string) + return -1; + + int32 length = Length(); + uint32 stringLength = strlen(string); + + // The following two checks are required to be compatible + // with BString: + if (length <= 0) + return -1; + + if (stringLength == 0) + return fromOffset; + + int32 stop = length - static_cast(stringLength); + int32 start = MAX(0, MIN(fromOffset, stop)); + int32 position = -1; + + + for (int32 i = start; i <= stop; i++) + if (string[0] == ByteAt(i)) + // This check is to avoid mute str*cmp() calls. Performance. + if (strncmp(string, String() + i, stringLength) == 0) { + position = i; + break; + } + + return position; +} + + +int32 +TrackerString::FindFirst(char ch) const +{ + char string[2] = {ch, '\0'}; + return FindFirst(string, 0); +} + + +int32 +TrackerString::FindFirst(char ch, int32 fromOffset) const +{ + char string[2] = {ch, '\0'}; + return FindFirst(string, fromOffset); +} + + +int32 +TrackerString::FindLast(const BString &string) const +{ + return FindLast(string.String(), Length() - 1); +} + + +int32 +TrackerString::FindLast(const char *string) const +{ + return FindLast(string, Length() - 1); +} + + +int32 +TrackerString::FindLast(const BString &string, int32 beforeOffset) const +{ + return FindLast(string.String(), beforeOffset); +} + + +int32 +TrackerString::FindLast(const char *string, int32 beforeOffset) const +{ + if (!string) + return -1; + + int32 length = Length(); + uint32 stringLength = strlen(string); + + // The following two checks are required to be compatible + // with BString: + if (length <= 0) + return -1; + + if (stringLength == 0) + return beforeOffset; + + int32 start = MIN(beforeOffset, length - static_cast(stringLength)); + int32 stop = 0; + int32 position = -1; + + for (int32 i = start; i >= stop; i--) + if (string[0] == ByteAt(i)) + // This check is to avoid mute str*cmp() calls. Performance. + if (strncmp(string, String() + i, stringLength) == 0) { + position = i; + break; + } + + return position; +} + + +int32 +TrackerString::FindLast(char ch) const +{ + char string[2] = {ch, '\0'}; + return FindLast(string, Length() - 1); +} + + +int32 +TrackerString::FindLast(char ch, int32 beforeOffset) const +{ + char string[2] = {ch, '\0'}; + return FindLast(string, beforeOffset); +} + + +int32 +TrackerString::IFindFirst(const BString &string) const +{ + return IFindFirst(string.String(), 0); +} + + +int32 +TrackerString::IFindFirst(const char *string) const +{ + return IFindFirst(string, 0); +} + + +int32 +TrackerString::IFindFirst(const BString &string, int32 fromOffset) const +{ + return IFindFirst(string.String(), fromOffset); +} + + +int32 +TrackerString::IFindFirst(const char *string, int32 fromOffset) const +{ + if (!string) + return -1; + + int32 length = Length(); + uint32 stringLength = strlen(string); + + // The following two checks are required to be compatible + // with BString: + if (length <= 0) + return -1; + + if (stringLength == 0) + return fromOffset; + + int32 stop = length - static_cast(stringLength); + int32 start = MAX(0, MIN(fromOffset, stop)); + int32 position = -1; + + for (int32 i = start; i <= stop; i++) + if (tolower(string[0]) == tolower(ByteAt(i))) + // This check is to avoid mute str*cmp() calls. Performance. + if (strncasecmp(string, String() + i, stringLength) == 0) { + position = i; + break; + } + + return position; +} + + +int32 +TrackerString::IFindLast(const BString &string) const +{ + return IFindLast(string.String(), Length() - 1); +} + + +int32 +TrackerString::IFindLast(const char *string) const +{ + return IFindLast(string, Length() - 1); +} + + +int32 +TrackerString::IFindLast(const BString &string, int32 beforeOffset) const +{ + return IFindLast(string.String(), beforeOffset); +} + + +int32 +TrackerString::IFindLast(const char *string, int32 beforeOffset) const +{ + if (!string) + return -1; + + int32 length = Length(); + uint32 stringLength = strlen(string); + + // The following two checks are required to be compatible + // with BString: + if (length <= 0) + return -1; + + if (stringLength == 0) + return beforeOffset; + + int32 start = MIN(beforeOffset, length - static_cast(stringLength)); + int32 stop = 0; + int32 position = -1; + + for (int32 i = start; i >= stop; i--) + if (tolower(string[0]) == tolower(ByteAt(i))) + // This check is to avoid mute str*cmp() calls. Performance. + if (strncasecmp(string, String() + i, stringLength) == 0) { + position = i; + break; + } + + return position; +} + + +// MatchesBracketExpression() assumes 'pattern' to point to the +// character following the initial '[' in a bracket expression. +// The reason is that an encountered '[' will be taken literally. +// (Makes it possible to match a '[' with the expression '[[]'). +bool +TrackerString::MatchesBracketExpression(const char *string, const char *pattern, + bool caseSensitivity) const +{ + bool GlyphMatch = IsStartOfGlyph(string[0]); + + if (IsInsideGlyph(string[0])) + return false; + + char testChar = ConditionalToLower(string[0], caseSensitivity); + bool match = false; + + bool inverse = *pattern == '^' || *pattern == '!'; + // We allow both ^ and ! as a initial inverting character. + + if (inverse) + pattern++; + + while (!match && *pattern != ']' && *pattern != '\0') { + switch (*pattern) { + case '-': + { + char start = ConditionalToLower(*(pattern - 1), caseSensitivity), + stop = ConditionalToLower(*(pattern + 1), caseSensitivity); + + if (IsGlyph(start) || IsGlyph(stop)) + return false; + // Not a valid range! + + if (islower(start) && islower(stop) + || isupper(start) && isupper(stop) + || isdigit(start) && isdigit(stop)) + // Make sure 'start' and 'stop' are of the same type. + match = start <= testChar && testChar <= stop; + else + return false; + // If no valid range, we've got a syntax error. + } + break; + + default: + if (GlyphMatch) + match = UTF8CharsAreEqual(string, pattern); + else + match = CharsAreEqual(testChar, *pattern, caseSensitivity); + break; + } + + if (!match) { + pattern++; + if (IsInsideGlyph(pattern[0])) + pattern = MoveToEndOfGlyph(pattern); + } + } + // Consider an unmatched bracket a failure + // (i.e. when detecting a '\0' instead of a ']'.) + if (*pattern == '\0') + return false; + + return (match ^ inverse) != 0; +} + + +bool +TrackerString::StringMatchesPattern(const char *string, const char *pattern, + bool caseSensitivity) const +{ + // One could do this dynamically, counting the number of *'s, + // but then you have to free them at every exit of this + // function, which is awkward and ugly. + const int32 kWildCardMaximum = 100; + const char *pStorage[kWildCardMaximum]; + const char *sStorage[kWildCardMaximum]; + + int32 patternLevel = 0; + + if (string == NULL || pattern == NULL) + return false; + + while (*pattern != '\0') { + + switch (*pattern) { + + case '?': + pattern++; + string++; + if (IsInsideGlyph(string[0])) + string = MoveToEndOfGlyph(string); + break; + + case '*': + { + // Collapse any ** and *? constructions: + while (*pattern == '*' || *pattern == '?') { + pattern++; + if (*pattern == '?' && string != '\0') { + string++; + if (IsInsideGlyph(string[0])) + string = MoveToEndOfGlyph(string); + } + } + + if (*pattern == '\0') + // An ending * matches all strings. + return true; + + bool match = false; + const char *pBefore = pattern - 1; + + if (*pattern == '[') { + pattern++; + + while (!match && *string != '\0') + match = MatchesBracketExpression(string++, pattern, caseSensitivity); + + // Skip the rest of the bracket: + while (*pattern != ']' && *pattern != '\0') + pattern++; + + // Failure if no closing bracket; + if (*pattern == '\0') + return false; + + } + else { + // No bracket, just one character: + while (!match && *string != '\0') { + if (IsGlyph(string[0])) + match = UTF8CharsAreEqual(string++, pattern); + else + match = CharsAreEqual(*string++, *pattern, caseSensitivity); + } + } + if (!match) + return false; + else { + pStorage[patternLevel] = pBefore; + if (IsInsideGlyph(string[0])) + string = MoveToEndOfGlyph(string); + sStorage[patternLevel++] = string; + if (patternLevel > kWildCardMaximum) + return false; + pattern++; + if (IsInsideGlyph(pattern[0])) + pattern = MoveToEndOfGlyph(pattern); + } + } + break; + + case '[': + pattern++; + + if (!MatchesBracketExpression(string, pattern, caseSensitivity)) + if (patternLevel > 0) { + pattern = pStorage[--patternLevel]; + string = sStorage[patternLevel]; + } else + return false; + else { + + // Skip the rest of the bracket: + while (*pattern != ']' && *pattern != '\0') + pattern++; + + // Failure if no closing bracket; + if (*pattern == '\0') + return false; + + string++; + if (IsInsideGlyph(string[0])) + string = MoveToEndOfGlyph(string); + pattern++; + } + break; + + default: + { + bool equal = false; + if (IsGlyph(string[0])) + equal = UTF8CharsAreEqual(string, pattern); + else + equal = CharsAreEqual(*string, *pattern, caseSensitivity); + + if (equal) { + pattern++; + if (IsInsideGlyph(pattern[0])) + pattern = MoveToEndOfGlyph(pattern); + string++; + if (IsInsideGlyph(string[0])) + string = MoveToEndOfGlyph(string); + } else if (patternLevel > 0) { + pattern = pStorage[--patternLevel]; + string = sStorage[patternLevel]; + } else + return false; + } + break; + } + + if (*pattern == '\0' && *string != '\0' && patternLevel > 0) { + pattern = pStorage[--patternLevel]; + string = sStorage[patternLevel]; + } + } + + return *string == '\0' && *pattern == '\0'; +} + + +bool +TrackerString::UTF8CharsAreEqual(const char *string1, const char *string2) const +{ + const char *s1 = string1; + const char *s2 = string2; + + if (IsStartOfGlyph(*s1) && *s1 == *s2) { + s1++; + s2++; + + while (IsInsideGlyph(*s1) && *s1 == *s2) { + s1++; + s2++; + } + + return !IsInsideGlyph(*s1) && !IsInsideGlyph(*s2) && *(s1 - 1) == *(s2 - 1); + + } else + return false; +} + + +const char * +TrackerString::MoveToEndOfGlyph(const char *string) const +{ + const char *ptr = string; + + while (IsInsideGlyph(*ptr)) + ptr++; + + return ptr; +} + + +bool +TrackerString::IsGlyph(char ch) const +{ + return (ch & 0x80) == 0x80; +} + + +bool +TrackerString::IsInsideGlyph(char ch) const +{ + return (ch & 0xC0) == 0x80; +} + + +bool +TrackerString::IsStartOfGlyph(char ch) const +{ + return (ch & 0xC0) == 0xC0; +} diff --git a/src/kits/tracker/TrackerString.h b/src/kits/tracker/TrackerString.h new file mode 100644 index 0000000000..ed32241ec2 --- /dev/null +++ b/src/kits/tracker/TrackerString.h @@ -0,0 +1,153 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef _TRACKER_STRING_H +#define _TRACKER_STRING_H + +#include + +#include +#include + +#include "RegExp.h" + +namespace BPrivate { + +enum TrackerStringExpressionType { + kNone = B_ERROR, + kStartsWith = 0, + kEndsWith, + kContains, + kGlobMatch, + kRegexpMatch +}; + +class TrackerString : public BString +{ +public: + TrackerString(); + TrackerString(const char *); + TrackerString(const TrackerString &); + TrackerString(const char *, int32 maxLength); + ~TrackerString(); + + bool Matches(const char *, bool caseSensitivity = false, + TrackerStringExpressionType expressionType = kGlobMatch) const; + + bool MatchesRegExp(const char *, bool caseSensitivity = true) const; + bool MatchesRegExp(const RegExp &) const; + bool MatchesRegExp(const RegExp *) const; + + bool MatchesGlob(const char *, bool caseSensitivity = false) const; + bool EndsWith(const char *, bool caseSensitivity = false) const; + bool StartsWith(const char *, bool caseSensitivity = false) const; + bool Contains(const char *, bool caseSensitivity = false) const; + + int32 FindFirst(const BString &) const; + int32 FindFirst(const char *) const; + int32 FindFirst(const BString &, int32 fromOffset) const; + int32 FindFirst(const char *, int32 fromOffset) const; + int32 FindFirst(char) const; + int32 FindFirst(char, int32 fromOffset) const; + + int32 FindLast(const BString &) const; + int32 FindLast(const char *) const; + int32 FindLast(const BString &, int32 beforeOffset) const; + int32 FindLast(const char *, int32 beforeOffset) const; + int32 FindLast(char) const; + int32 FindLast(char, int32 beforeOffset) const; + + int32 IFindFirst(const BString &) const; + int32 IFindFirst(const char *) const; + int32 IFindFirst(const BString &, int32 fromOffset) const; + int32 IFindFirst(const char *, int32 fromOffset) const; + + int32 IFindLast(const BString &) const; + int32 IFindLast(const char *) const; + int32 IFindLast(const BString &, int32 beforeOffset) const; + int32 IFindLast(const char *, int32 beforeOffset) const; + +private: + bool IsGlyph(char) const; + bool IsInsideGlyph(char) const; // Not counting start! + bool IsStartOfGlyph(char) const; + const char *MoveToEndOfGlyph(const char *) const; + + // Functions for Glob matching: + bool MatchesBracketExpression(const char *string, const char *pattern, + bool caseSensitivity) const; + bool StringMatchesPattern(const char *string, const char *pattern, + bool caseSensitivity) const; + + char ConditionalToLower(char c, bool toLower) const; + bool CharsAreEqual(char char1, char char2, bool toLower) const; + bool UTF8CharsAreEqual(const char *string1, const char *string2) const; +}; + +inline bool +TrackerString::MatchesRegExp(const RegExp *expression) const +{ + if (expression == NULL || expression->InitCheck() != B_OK) + return false; + + return expression->Matches(*this); +} + +inline bool +TrackerString::MatchesRegExp(const RegExp &expression) const +{ + if (expression.InitCheck() != B_OK) + return false; + + return expression.Matches(*this); +} + +inline char +TrackerString::ConditionalToLower(char c, bool caseSensitivity) const +{ + return caseSensitivity ? c : (char)tolower(c); +} + +inline bool +TrackerString::CharsAreEqual(char char1, char char2, bool caseSensitivity) const +{ + return ConditionalToLower(char1, caseSensitivity) + == ConditionalToLower(char2, caseSensitivity); +} + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/TrashWatcher.cpp b/src/kits/tracker/TrashWatcher.cpp new file mode 100644 index 0000000000..c29435a6a7 --- /dev/null +++ b/src/kits/tracker/TrashWatcher.cpp @@ -0,0 +1,219 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include + +#include +#include +#include +#include +#include +#include + +#include "Attributes.h" +#include "Bitmaps.h" +#include "FSUtils.h" +#include "Tracker.h" +#include "TrashWatcher.h" + +BTrashWatcher::BTrashWatcher() + : BLooper("TrashWatcher", B_LOW_PRIORITY), + fTrashNodeList(20, true) +{ + FSCreateTrashDirs(); + WatchTrashDirs(); + fTrashFull = CheckTrashDirs(); + UpdateTrashIcons(); + + // watch volumes + TTracker::WatchNode(0, B_WATCH_MOUNT, this); +} + +BTrashWatcher::~BTrashWatcher() +{ + stop_watching(this); +} + +bool +BTrashWatcher::IsTrashNode(const node_ref *testNode) const +{ + int32 count = fTrashNodeList.CountItems(); + for (int32 index = 0; index < count; index++) { + node_ref *nref = fTrashNodeList.ItemAt(index); + if (nref->node == testNode->node && nref->device == testNode->device) + return true; + } + + return false; +} + +void +BTrashWatcher::MessageReceived(BMessage *message) +{ + if (message->what != B_NODE_MONITOR) { + _inherited::MessageReceived(message); + return; + } + + switch (message->FindInt32("opcode")) { + case B_ENTRY_CREATED: + if (!fTrashFull) { + fTrashFull = true; + UpdateTrashIcons(); + } + break; + + case B_ENTRY_MOVED: + { + // allow code to fall through if move is from/to trash + // but do nothing for moves in the same directory + ino_t toDir; + ino_t fromDir; + message->FindInt64("from directory", &fromDir); + message->FindInt64("to directory", &toDir); + if (fromDir == toDir) + break; + } + // fall thru + + case B_DEVICE_UNMOUNTED: + // fall thru + + case B_ENTRY_REMOVED: + { + bool full = CheckTrashDirs(); + if (fTrashFull != full) { + fTrashFull = full; + UpdateTrashIcons(); + } + break; + } + // We should handle DEVICE_UNMOUNTED here too to remove trash + + case B_DEVICE_MOUNTED: + { + dev_t device; + BDirectory trashDir; + if (message->FindInt32("new device", &device) == B_OK + && FSGetTrashDir(&trashDir, device) == B_OK) { + node_ref trashNode; + trashDir.GetNodeRef(&trashNode); + TTracker::WatchNode(&trashNode, B_WATCH_DIRECTORY, this); + fTrashNodeList.AddItem(new node_ref(trashNode)); + + // Check if the new volume has anything trashed. + if (CheckTrashDirs() && !fTrashFull) { + fTrashFull = true; + UpdateTrashIcons(); + } + } + break; + } + } +} + +void +BTrashWatcher::UpdateTrashIcons() +{ + + BVolume boot; + if (BVolumeRoster().GetBootVolume(&boot) != B_OK) + return; + + BDirectory trashDir; + if (FSGetTrashDir(&trashDir, boot.Device()) == B_OK) { + // pull out the icons for the current trash state from resources and + // apply them onto the trash directory node + size_t largeSize = 0; + size_t smallSize = 0; + const void *largeData = GetTrackerResources()->LoadResource('ICON', + fTrashFull ? kResTrashFullIcon : kResTrashIcon, &largeSize); + + const void *smallData = GetTrackerResources()->LoadResource('MICN', + fTrashFull ? kResTrashFullIcon : kResTrashIcon, &smallSize); + + if (largeData) + trashDir.WriteAttr(kAttrLargeIcon, B_COLOR_8_BIT_TYPE, 0, + largeData, largeSize); + else + TRESPASS(); + + if (smallData) + trashDir.WriteAttr(kAttrMiniIcon, B_COLOR_8_BIT_TYPE, 0, + smallData, smallSize); + else + TRESPASS(); + } +} + +void +BTrashWatcher::WatchTrashDirs() +{ + BVolumeRoster volRoster; + volRoster.Rewind(); + BVolume volume; + while (volRoster.GetNextVolume(&volume) == B_OK) { + if (volume.IsReadOnly() || !volume.IsPersistent()) + continue; + + BDirectory trashDir; + if (FSGetTrashDir(&trashDir, volume.Device()) == B_OK) { + node_ref trash_node; + trashDir.GetNodeRef(&trash_node); + watch_node(&trash_node, B_WATCH_DIRECTORY, this); + fTrashNodeList.AddItem(new node_ref(trash_node)); + } + } +} + +bool +BTrashWatcher::CheckTrashDirs() +{ + BVolumeRoster volRoster; + volRoster.Rewind(); + BVolume volume; + while (volRoster.GetNextVolume(&volume) == B_OK) { + if (volume.IsReadOnly() || !volume.IsPersistent()) + continue; + + BDirectory trashDir; + FSGetTrashDir(&trashDir, volume.Device()); + trashDir.Rewind(); + BEntry entry; + if (trashDir.GetNextEntry(&entry) == B_OK) + return true; + } + + return false; +} diff --git a/src/kits/tracker/TrashWatcher.h b/src/kits/tracker/TrashWatcher.h new file mode 100644 index 0000000000..d252eaea57 --- /dev/null +++ b/src/kits/tracker/TrashWatcher.h @@ -0,0 +1,70 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef _TRASH_WATCHER_H +#define _TRASH_WATCHER_H + +#include +#include "ObjectList.h" + +namespace BPrivate { + +class BTrashWatcher : public BLooper { +public: + // Trash watcher monitors the virtual trash folder contents + // and updates it's icon during a change + BTrashWatcher(); + virtual ~BTrashWatcher(); + + bool CheckTrashDirs(); + bool IsTrashNode(const node_ref *) const; + +protected: + virtual void MessageReceived(BMessage *); + +private: + void WatchTrashDirs(); + void UpdateTrashIcons(); + + bool fTrashFull; + BObjectList fTrashNodeList; + + typedef BLooper _inherited; +}; + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/Utilities.cpp b/src/kits/tracker/Utilities.cpp new file mode 100644 index 0000000000..f21827fe4e --- /dev/null +++ b/src/kits/tracker/Utilities.cpp @@ -0,0 +1,1427 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if B_BEOS_VERSION_DANO +#define _IMPEXP_BE +#endif +extern _IMPEXP_BE const uint32 LARGE_ICON_TYPE; +extern _IMPEXP_BE const uint32 MINI_ICON_TYPE; +#if B_BEOS_VERSION_DANO +#undef _IMPEXP_BE +#endif + +#include "Attributes.h" +#include "MimeTypes.h" +#include "Model.h" +#include "Utilities.h" +#include "ContainerWindow.h" + +#include + + + +FILE *logFile = NULL; + +namespace BPrivate { + +const rgb_color kBlack = {0, 0, 0, 255}; +const rgb_color kWhite = {255, 255, 255, 255}; + +uint32 +HashString(const char *string, uint32 seed) +{ + char ch; + uint32 result = seed; + + while((ch = *string++) != 0) { + result = (result << 7) ^ (result >> 24); + result ^= ch; + } + + result ^= result << 12; + return result; +} + +uint32 +AttrHashString(const char *string, uint32 type) +{ + char c; + uint32 hash = 0; + + while((c = *string++) != 0) { + hash = (hash << 7) ^ (hash >> 24); + hash ^= c; + } + + hash ^= hash << 12; + + hash &= ~0xff; + hash |= type; + + return hash; +} + +bool +ValidateStream(BMallocIO *stream, uint32 key, int32 version) +{ + uint32 test_key; + int32 test_version; + + if (stream->Read(&test_key, sizeof(uint32)) <= 0 + || stream->Read(&test_version, sizeof(int32)) <=0) + return false; + + return test_key == key && test_version == version; +} + +void +DisallowFilenameKeys(BTextView *textView) +{ + textView->DisallowChar(':'); + textView->DisallowChar('/'); +} + +void +DisallowMetaKeys(BTextView *textView) +{ + textView->DisallowChar(B_TAB); + textView->DisallowChar(B_ESCAPE); + textView->DisallowChar(B_INSERT); + textView->DisallowChar(B_DELETE); + textView->DisallowChar(B_HOME); + textView->DisallowChar(B_END); + textView->DisallowChar(B_PAGE_UP); + textView->DisallowChar(B_PAGE_DOWN); + textView->DisallowChar(B_FUNCTION_KEY); +} + +} // namespace BPrivate + +void +PoseInfo::EndianSwap(void *castToThis) +{ + PoseInfo *self = (PoseInfo *)castToThis; + + PRINT(("swapping PoseInfo\n")); + + STATIC_ASSERT(sizeof(ino_t) == sizeof(int64)); + self->fInitedDirectory = SwapInt64(self->fInitedDirectory); + swap_data(B_POINT_TYPE, &self->fLocation, sizeof(BPoint), B_SWAP_ALWAYS); + + // do a sanity check on the icon position + if (self->fLocation.x < -20000 || self->fLocation.x > 20000 + || self->fLocation.y < -20000 || self->fLocation.y > 20000) { + // position out of range, force autoplcemement + PRINT((" rejecting icon position out of range\n")); + self->fInitedDirectory = -1LL; + self->fLocation = BPoint(0, 0); + } +} + +void +PoseInfo::PrintToStream() +{ + PRINT(("%s, inode:%Lx, location %f %f\n", fInvisible ? "hidden" : "visible", + fInitedDirectory, fLocation.x, fLocation.y)); +} + +// #pragma mark - + +size_t +ExtendedPoseInfo::Size() const +{ + return sizeof(ExtendedPoseInfo) + fNumFrames * sizeof(FrameLocation); +} + +size_t +ExtendedPoseInfo::Size(int32 count) +{ + return sizeof(ExtendedPoseInfo) + count * sizeof(FrameLocation); +} + +size_t +ExtendedPoseInfo::SizeWithHeadroom() const +{ + return sizeof(ExtendedPoseInfo) + (fNumFrames + 1) * sizeof(FrameLocation); +} + +size_t +ExtendedPoseInfo::SizeWithHeadroom(size_t oldSize) +{ + int32 count = (ssize_t)oldSize - (ssize_t)sizeof(ExtendedPoseInfo); + if (count > 0) + count /= sizeof(FrameLocation); + else + count = 0; + + return Size(count + 1); +} + + +bool +ExtendedPoseInfo::HasLocationForFrame(BRect frame) const +{ + for (int32 index = 0; index < fNumFrames; index++) + if (fLocations[index].fFrame == frame) + return true; + + return false; +} + +BPoint +ExtendedPoseInfo::LocationForFrame(BRect frame) const +{ + for (int32 index = 0; index < fNumFrames; index++) + if (fLocations[index].fFrame == frame) + return fLocations[index].fLocation; + + TRESPASS(); + return BPoint(0, 0); +} + +bool +ExtendedPoseInfo::SetLocationForFrame(BPoint newLocation, BRect frame) +{ + for (int32 index = 0; index < fNumFrames; index++) + if (fLocations[index].fFrame == frame) { + if (fLocations[index].fLocation == newLocation) + return false; + fLocations[index].fLocation = newLocation; + return true; + } + fLocations[fNumFrames].fFrame = frame; + fLocations[fNumFrames].fLocation = newLocation; + fLocations[fNumFrames].fWorkspaces = 0xffffffff; + fNumFrames++; + return true; +} + +void +ExtendedPoseInfo::EndianSwap(void *castToThis) +{ + ExtendedPoseInfo *self = (ExtendedPoseInfo *)castToThis; + + PRINT(("swapping ExtendedPoseInfo\n")); + + self->fWorkspaces = SwapUInt32(self->fWorkspaces); + self->fNumFrames = SwapInt32(self->fNumFrames); + + for (int32 index = 0; index < self->fNumFrames; index++) { + swap_data(B_POINT_TYPE, &self->fLocations[index].fLocation, + sizeof(BPoint), B_SWAP_ALWAYS); + + if (self->fLocations[index].fLocation.x < -20000 + || self->fLocations[index].fLocation.x > 20000 + || self->fLocations[index].fLocation.y < -20000 + || self->fLocations[index].fLocation.y > 20000) { + // position out of range, force autoplcemement + PRINT((" rejecting icon position out of range\n")); + self->fLocations[index].fLocation = BPoint(0, 0); + } + swap_data(B_RECT_TYPE, &self->fLocations[index].fFrame, + sizeof(BRect), B_SWAP_ALWAYS); + } +} + +void +ExtendedPoseInfo::PrintToStream() +{ +} + +// #pragma mark - + +OffscreenBitmap::OffscreenBitmap(BRect frame) + : fBitmap(NULL) +{ + NewBitmap(frame); +} + +OffscreenBitmap::OffscreenBitmap() + : fBitmap(NULL) +{ +} + +OffscreenBitmap::~OffscreenBitmap() +{ + delete fBitmap; +} + +void +OffscreenBitmap::NewBitmap(BRect bounds) +{ + delete fBitmap; + fBitmap = new BBitmap(bounds, B_COLOR_8_BIT, true); + if (fBitmap->Lock()) { + BView *view = new BView(fBitmap->Bounds(), "", B_FOLLOW_NONE, 0); + fBitmap->AddChild(view); + + BRect clipRect = view->Bounds(); + BRegion newClip; + newClip.Set(clipRect); + view->ConstrainClippingRegion(&newClip); + + fBitmap->Unlock(); + } else { + delete fBitmap; + fBitmap = NULL; + } +} + +BView * +OffscreenBitmap::BeginUsing(BRect frame) +{ + if (!fBitmap || fBitmap->Bounds() != frame) + NewBitmap(frame); + fBitmap->Lock(); + return View(); +} + +void +OffscreenBitmap::DoneUsing() +{ + fBitmap->Unlock(); +} + +BBitmap * +OffscreenBitmap::Bitmap() const +{ + ASSERT(fBitmap); + ASSERT(fBitmap->IsLocked()); + return fBitmap; +} + +BView * +OffscreenBitmap::View() const +{ + ASSERT(fBitmap); + return fBitmap->ChildAt(0); +} + + +// #pragma mark - + + +namespace BPrivate { + +/** Changes the alpha value of the given bitmap to create a nice + * horizontal fade out in the specified region. + * "from" is always transparent, "to" opaque. + */ + +void +FadeRGBA32Horizontal(uint32 *bits, int32 width, int32 height, int32 from, int32 to) +{ + // check parameters + if (width < 0 || height < 0 || from < 0 || to < 0) + return; + + float change = 1.f / (to - from); + if (from > to) { + int32 temp = from; + from = to; + to = temp; + } + + for (int32 y = 0; y < height; y++) { + float alpha = change > 0 ? 0.0f : 1.0f; + + for (int32 x = from; x <= to; x++) { + if (bits[x] & 0xff000000) { + uint32 a = uint32((bits[x] >> 24) * alpha); + bits[x] = (bits[x] & 0x00ffffff) | (a << 24); + } + alpha += change; + } + bits += width; + } +} + + +/** Changes the alpha value of the given bitmap to create a nice + * vertical fade out in the specified region. + * "from" is always transparent, "to" opaque. + */ + +void +FadeRGBA32Vertical(uint32 *bits, int32 width, int32 height, int32 from, int32 to) +{ + // check parameters + if (width < 0 || height < 0 || from < 0 || to < 0) + return; + + if (from > to) + bits += width * (height - (from - to)); + + float change = 1.f / (to - from); + if (from > to) { + int32 temp = from; + from = to; + to = temp; + } + + float alpha = change > 0 ? 0.0f : 1.0f; + + for (int32 y = from; y <= to; y++) { + for (int32 x = 0; x < width; x++) { + if (bits[x] & 0xff000000) { + uint32 a = uint32((bits[x] >> 24) * alpha); + bits[x] = (bits[x] & 0x00ffffff) | (a << 24); + } + } + alpha += change; + bits += width; + } +} + +} // namespace BPrivate + + +// #pragma mark - + + +DraggableIcon::DraggableIcon(BRect rect, const char *name, const char *mimeType, + icon_size size, const BMessage *message, BMessenger target, uint32 resizeMask, + uint32 flags) + : BView(rect, name, resizeMask, flags), + fMessage(*message), + fTarget(target) +{ + fBitmap = new BBitmap(Bounds(), kDefaultIconDepth); + BMimeType mime(mimeType); + status_t error = mime.GetIcon(fBitmap, size); + ASSERT(mime.IsValid()); + if (error != B_OK) { + PRINT(("failed to get icon for %s, %s\n", mimeType, strerror(error))); + BMimeType mime(B_FILE_MIMETYPE); + ASSERT(mime.IsInstalled()); + mime.GetIcon(fBitmap, size); + } +} + + +void +DraggableIcon::SetTarget(BMessenger target) +{ + fTarget = target; +} + +DraggableIcon::~DraggableIcon() +{ + delete fBitmap; +} + +BRect +DraggableIcon::PreferredRect(BPoint offset, icon_size size) +{ + BRect result(0, 0, size - 1, size - 1); + result.OffsetTo(offset); + return result; +} + +void +DraggableIcon::AttachedToWindow() +{ + BView *parent = Parent(); + if (parent) { + SetViewColor(parent->ViewColor()); + SetLowColor(parent->LowColor()); + } +} + +void +DraggableIcon::MouseDown(BPoint point) +{ + if (!DragStarted(&fMessage)) + return; + + BRect rect(Bounds()); + BBitmap *dragBitmap = new BBitmap(rect, B_RGBA32, true); + dragBitmap->Lock(); + BView *view = new BView(dragBitmap->Bounds(), "", B_FOLLOW_NONE, 0); + dragBitmap->AddChild(view); + view->SetOrigin(0, 0); + BRect clipRect(view->Bounds()); + BRegion newClip; + newClip.Set(clipRect); + view->ConstrainClippingRegion(&newClip); + + // Transparent draw magic + view->SetHighColor(0, 0, 0, 0); + view->FillRect(view->Bounds()); + view->SetDrawingMode(B_OP_ALPHA); + view->SetHighColor(0, 0, 0, 128); // set the level of transparency by + // value + view->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_COMPOSITE); + view->DrawBitmap(fBitmap); + view->Sync(); + dragBitmap->Unlock(); + DragMessage(&fMessage, dragBitmap, B_OP_ALPHA, point, fTarget.Target(0)); +} + +bool +DraggableIcon::DragStarted(BMessage *) +{ + return true; +} + +void +DraggableIcon::Draw(BRect) +{ + SetDrawingMode(B_OP_OVER); + DrawBitmap(fBitmap); +} + +// #pragma mark - + +FlickerFreeStringView::FlickerFreeStringView(BRect bounds, const char *name, + const char *text, uint32 resizeFlags, uint32 flags) + : BStringView(bounds, name, text, resizeFlags, flags), + fBitmap(NULL), + fOrigBitmap(NULL) +{ +} + +FlickerFreeStringView::FlickerFreeStringView(BRect bounds, const char *name, + const char *text, BBitmap *inBitmap, uint32 resizeFlags, uint32 flags) + : BStringView(bounds, name, text, resizeFlags, flags), + fBitmap(NULL), + fOrigBitmap(inBitmap) +{ +} + +FlickerFreeStringView::~FlickerFreeStringView() +{ + delete fBitmap; +} + +void +FlickerFreeStringView::Draw(BRect) +{ + BRect bounds(Bounds()); + if (!fBitmap) + fBitmap = new OffscreenBitmap(Bounds()); + + BView *offscreen = fBitmap->BeginUsing(bounds); + + if (Parent()) { + fViewColor = Parent()->ViewColor(); + fLowColor = Parent()->ViewColor(); + } + + offscreen->SetViewColor(fViewColor); + offscreen->SetHighColor(HighColor()); + offscreen->SetLowColor(fLowColor); + + BFont font; + GetFont(&font); + offscreen->SetFont(&font); + + offscreen->Sync(); + if (fOrigBitmap) + offscreen->DrawBitmap(fOrigBitmap, Frame(), bounds); + else + offscreen->FillRect(bounds, B_SOLID_LOW); + + if (Text()) { + BPoint loc; + + font_height height; + GetFontHeight(&height); + + edge_info eInfo; + switch (Alignment()) { + case B_ALIGN_LEFT: + { + // If the first char has a negative left edge give it + // some more room by shifting that much more to the right. + font.GetEdges(Text(), 1, &eInfo); + loc.x = bounds.left + (2 - eInfo.left); + break; + } + + case B_ALIGN_CENTER: + { + float width = StringWidth(Text()); + float center = (bounds.right - bounds.left) / 2; + loc.x = center - (width/2); + break; + } + + case B_ALIGN_RIGHT: + { + float width = StringWidth(Text()); + loc.x = bounds.right - width - 2; + break; + } + } + loc.y = bounds.bottom - (1 + height.descent); + offscreen->MovePenTo(loc); + offscreen->DrawString(Text()); + } + offscreen->Sync(); + SetDrawingMode(B_OP_COPY); + DrawBitmap(fBitmap->Bitmap()); + fBitmap->DoneUsing(); +} + +void +FlickerFreeStringView::AttachedToWindow() +{ + _inherited::AttachedToWindow(); + if (Parent()) { + fViewColor = Parent()->ViewColor(); + fLowColor = Parent()->ViewColor(); + } + SetViewColor(B_TRANSPARENT_32_BIT); + SetLowColor(B_TRANSPARENT_32_BIT); +} + +void +FlickerFreeStringView::SetViewColor(rgb_color color) +{ + if (fViewColor != color) { + fViewColor = color; + Invalidate(); + } + _inherited::SetViewColor(B_TRANSPARENT_32_BIT); +} + +void +FlickerFreeStringView::SetLowColor(rgb_color color) +{ + if (fLowColor != color) { + fLowColor = color; + Invalidate(); + } + _inherited::SetLowColor(B_TRANSPARENT_32_BIT); +} + +// #pragma mark - + +TitledSeparatorItem::TitledSeparatorItem(const char *label) + : BMenuItem(label, 0) +{ + _inherited::SetEnabled(false); +} + + +TitledSeparatorItem::~TitledSeparatorItem() +{ +} + +void +TitledSeparatorItem::SetEnabled(bool) +{ + // leave disabled +} + +void +TitledSeparatorItem::GetContentSize(float *width, float *height) +{ + _inherited::GetContentSize(width, height); +} + +const float kMinSeparatorStubX = 10; +const float kStubToStringSlotX = 5; + +inline rgb_color +ShiftMenuBackgroundColor(float by) +{ + return tint_color(ui_color(B_MENU_BACKGROUND_COLOR), by); +} + +void +TitledSeparatorItem::Draw() +{ + BRect frame(Frame()); + + BMenu *parent = Menu(); + ASSERT(parent); + + menu_info minfo; + get_menu_info(&minfo); + + if (minfo.separator > 0) { + frame.left += 10; + frame.right -= 10; + } else { + frame.left += 1; + frame.right -= 1; + } + + float startX = frame.left; + float endX = frame.right; + + float maxStringWidth = endX - startX - (2 * kMinSeparatorStubX + + 2 * kStubToStringSlotX); + + // ToDo: + // handle case where maxStringWidth turns out negative here + + BString truncatedLabel(Label()); + parent->TruncateString(&truncatedLabel, B_TRUNCATE_END, maxStringWidth); + + maxStringWidth = parent->StringWidth(truncatedLabel.String()); + + // first calculate the length of the stub part of the + // divider line, so we can use it for secondStartX + float firstEndX = ((endX - startX) - maxStringWidth) / 2 - kStubToStringSlotX; + if (firstEndX < 0) + firstEndX = 0; + + float secondStartX = endX - firstEndX; + + // now finish calculating firstEndX + firstEndX += startX; + + parent->PushState(); + + int32 y = (int32) (frame.top + (frame.bottom - frame.top) / 2); + + parent->BeginLineArray(minfo.separator == 2 ? 6 : 4); + parent->AddLine(BPoint(frame.left, y), BPoint(firstEndX, y), + ShiftMenuBackgroundColor(B_DARKEN_1_TINT)); + parent->AddLine(BPoint(secondStartX, y), BPoint(frame.right, y), + ShiftMenuBackgroundColor(B_DARKEN_1_TINT)); + + if (minfo.separator == 2) { + y++; + frame.left++; + frame.right--; + parent->AddLine(BPoint(frame.left,y), BPoint(firstEndX, y), + ShiftMenuBackgroundColor(B_DARKEN_1_TINT)); + parent->AddLine(BPoint(secondStartX,y), BPoint(frame.right, y), + ShiftMenuBackgroundColor(B_DARKEN_1_TINT)); + } + y++; + if (minfo.separator == 2) { + frame.left++; + frame.right--; + } + parent->AddLine(BPoint(frame.left, y), BPoint(firstEndX, y), + ShiftMenuBackgroundColor(B_DARKEN_1_TINT)); + parent->AddLine(BPoint(secondStartX, y), BPoint(frame.right, y), + ShiftMenuBackgroundColor(B_DARKEN_1_TINT)); + + parent->EndLineArray(); + + font_height finfo; + parent->GetFontHeight(&finfo); + + parent->SetLowColor(parent->ViewColor()); + BPoint loc(firstEndX + kStubToStringSlotX, ContentLocation().y + finfo.ascent); + + parent->MovePenTo(loc + BPoint(1, 1)); + parent->SetHighColor(ShiftMenuBackgroundColor(B_DARKEN_1_TINT)); + parent->DrawString(truncatedLabel.String()); + + parent->MovePenTo(loc); + parent->SetHighColor(ShiftMenuBackgroundColor(B_DISABLED_LABEL_TINT)); + parent->DrawString(truncatedLabel.String()); + + parent->PopState(); +} + +// #pragma mark - + +ShortcutFilter::ShortcutFilter(uint32 shortcutKey, uint32 shortcutModifier, + uint32 shortcutWhat, BHandler *target) + : BMessageFilter(B_KEY_DOWN), + fShortcutKey(shortcutKey), + fShortcutModifier(shortcutModifier), + fShortcutWhat(shortcutWhat), + fTarget(target) +{ +} + +filter_result +ShortcutFilter::Filter(BMessage *message, BHandler **) +{ + if (message->what == B_KEY_DOWN) { + uint32 modifiers; + uint32 rawKeyChar = 0; + uint8 byte = 0; + int32 key = 0; + + if (message->FindInt32("modifiers", (int32 *)&modifiers) != B_OK + || message->FindInt32("raw_char", (int32 *)&rawKeyChar) != B_OK + || message->FindInt8("byte", (int8 *)&byte) != B_OK + || message->FindInt32("key", &key) != B_OK) + return B_DISPATCH_MESSAGE; + + modifiers &= B_SHIFT_KEY | B_COMMAND_KEY | B_CONTROL_KEY + | B_OPTION_KEY | B_MENU_KEY; + // strip caps lock, etc. + + if (modifiers == fShortcutModifier && rawKeyChar == fShortcutKey) { + fTarget->Looper()->PostMessage(fShortcutWhat, fTarget); + return B_SKIP_MESSAGE; + } + } + + // let others deal with this + return B_DISPATCH_MESSAGE; +} + +// #pragma mark - +namespace BPrivate { + +void +EmbedUniqueVolumeInfo(BMessage *message, const BVolume *volume) +{ + BDirectory rootDirectory; + time_t created; + fs_info info; + + if (volume->GetRootDirectory(&rootDirectory) == B_OK + && rootDirectory.GetCreationTime(&created) == B_OK + && fs_stat_dev(volume->Device(), &info) == 0) { + message->AddInt32("creationDate", created); + message->AddInt64("capacity", volume->Capacity()); + message->AddString("deviceName", info.device_name); + message->AddString("volumeName", info.volume_name); + message->AddString("fshName", info.fsh_name); + } +} + + +status_t +MatchArchivedVolume(BVolume *result, const BMessage *message, int32 index) +{ + time_t created; + off_t capacity; + + if (message->FindInt32("creationDate", index, &created) != B_OK + || message->FindInt64("capacity", index, &capacity) != B_OK) + return B_ERROR; + + BVolumeRoster roster; + BVolume volume; + BString deviceName, volumeName, fshName; + + if (message->FindString("deviceName", &deviceName) == B_OK + && message->FindString("volumeName", &volumeName) == B_OK + && message->FindString("fshName", &fshName) == B_OK) { + // New style volume identifiers: We have a couple of characteristics, + // and compute a score from them. The volume with the greatest score + // (if over a certain threshold) is the one we're looking for. We + // pick the first volume, in case there is more than one with the + // same score. + dev_t foundDevice = -1; + int foundScore = -1; + roster.Rewind(); + while (roster.GetNextVolume(&volume) == B_OK) { + if (volume.IsPersistent() && volume.KnowsQuery()) { + // get creation time and fs_info + BDirectory root; + volume.GetRootDirectory(&root); + time_t cmpCreated; + fs_info info; + if (root.GetCreationTime(&cmpCreated) == B_OK + && fs_stat_dev(volume.Device(), &info) == 0) { + // compute the score + int score = 0; + + // creation time + if (created == cmpCreated) + score += 5; + // capacity + if (capacity == volume.Capacity()) + score += 4; + // device name + if (deviceName == info.device_name) + score += 3; + // volume name + if (volumeName == info.volume_name) + score += 2; + // fsh name + if (fshName == info.fsh_name) + score += 1; + + // check score + if (score >= 9 && score > foundScore) { + foundDevice = volume.Device(); + foundScore = score; + } + } + } + } + if (foundDevice >= 0) + return result->SetTo(foundDevice); + } else { + // Old style volume identifiers: We have only creation time and + // capacity. Both must match. + roster.Rewind(); + while (roster.GetNextVolume(&volume) == B_OK) + if (volume.IsPersistent() && volume.KnowsQuery()) { + BDirectory root; + volume.GetRootDirectory(&root); + time_t cmpCreated; + root.GetCreationTime(&cmpCreated); + if (created == cmpCreated && capacity == volume.Capacity()) { + *result = volume; + return B_OK; + } + } + } + + return B_DEV_BAD_DRIVE_NUM; +} + +void +StringFromStream(BString *string, BMallocIO *stream, bool endianSwap) +{ + int32 length; + stream->Read(&length, sizeof(length)); + if (endianSwap) + length = SwapInt32(length); + + if (length <= 0 || length > 10000) { + // ToDo: + // should fail here + PRINT(("problems instatiating a string, length probably wrong %d\n", length)); + return; + } + + char *buffer = string->LockBuffer(length); + stream->Read(buffer, (size_t)length + 1); + string->UnlockBuffer(length); +} + +void +StringToStream(const BString *string, BMallocIO *stream) +{ + int32 length = string->Length(); + stream->Write(&length, sizeof(int32)); + stream->Write(string->String(), (size_t)string->Length() + 1); +} + +int32 +ArchiveSize(const BString *string) +{ + return string->Length() + 1 + (ssize_t)sizeof(int32); +} + +int32 +CountRefs(const BMessage *message) +{ + uint32 type; + int32 count; + message->GetInfo("refs", &type, &count); + + return count; +} + +static entry_ref * +EachEntryRefCommon(BMessage *message, entry_ref *(*func)(entry_ref *, void *), + void *passThru, int32 maxCount) +{ + uint32 type; + int32 count; + message->GetInfo("refs", &type, &count); + + if (maxCount >= 0 && count > maxCount) + count = maxCount; + + for (int32 index = 0; index < count; index++) { + entry_ref ref; + message->FindRef("refs", index, &ref); + entry_ref *result = (func)(&ref, passThru); + if (result) + return result; + } + + return NULL; +} + +bool +ContainsEntryRef(const BMessage *message, const entry_ref *ref) +{ + entry_ref match; + for (int32 index = 0; (message->FindRef("refs", index, &match) == B_OK); index++) + if (*ref == match) + return true; + + return false; +} + +entry_ref * +EachEntryRef(BMessage *message, entry_ref *(*func)(entry_ref *, void *), + void *passThru) +{ + return EachEntryRefCommon(message, func, passThru, -1); +} + +typedef entry_ref *(*EachEntryIteratee)(entry_ref *, void *); + +const entry_ref *EachEntryRef(const BMessage *message, + const entry_ref *(*func)(const entry_ref *, void *), void *passThru) +{ + return EachEntryRefCommon(const_cast(message), + (EachEntryIteratee)func, passThru, -1); +} + +entry_ref * +EachEntryRef(BMessage *message, entry_ref *(*func)(entry_ref *, void *), + void *passThru, int32 maxCount) +{ + return EachEntryRefCommon(message, func, passThru, maxCount); +} + +const entry_ref * +EachEntryRef(const BMessage *message, const entry_ref *(*func)(const entry_ref *, void *), + void *passThru, int32 maxCount) +{ + return EachEntryRefCommon(const_cast(message), + (EachEntryIteratee)func, passThru, maxCount); +} + +void +TruncateLeaf(BString *string) +{ + for (int32 index = string->Length(); index >= 0; index--) + if ((*string)[index] == '/') { + string->Truncate(index + 1); + return; + } +} + +int64 +StringToScalar(const char *text) +{ + char *end; + int64 val; + + char *buffer = new char [strlen(text) + 1]; + strcpy(buffer, text); + + if (strstr(buffer, "k") || strstr(buffer, "K")) { + val = strtoll(buffer, &end, 10); + val *= kKBSize; + } else if (strstr(buffer, "mb") || strstr(buffer, "MB")) { + val = strtoll(buffer, &end, 10); + val *= kMBSize; + } else if (strstr(buffer, "gb") || strstr(buffer, "GB")) { + val = strtoll(buffer, &end, 10); + val *= kGBSize; + } else if (strstr(buffer, "byte") || strstr(buffer, "BYTE")) { + val = strtoll(buffer, &end, 10); + val *= kGBSize; + } else + // no suffix, try plain byte conversion + val = strtoll(buffer, &end, 10); + + delete [] buffer; + return val; +} + +#if B_BEOS_VERSION <= B_BEOS_VERSION_MAUI + +bool +operator==(const rgb_color &a, const rgb_color &b) +{ + return a.red == b.red + && a.green == b.green + && a.blue == b.blue + && a.alpha == b.alpha; +} + +bool +operator!=(const rgb_color &a, const rgb_color &b) +{ + return !operator==(a, b); +} + +#endif + +static BRect +LineBounds(BPoint where, float length, bool vertical) +{ + BRect result; + result.SetLeftTop(where); + result.SetRightBottom(where + BPoint(2, 2)); + if (vertical) + result.bottom = result.top + length; + else + result.right = result.left + length; + + return result; +} + +SeparatorLine::SeparatorLine(BPoint where, float length, bool vertical, const char *name) + : BView(LineBounds(where, length, vertical), name, + B_FOLLOW_LEFT | B_FOLLOW_TOP, B_WILL_DRAW) +{ + SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); + SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR)); +} + +void +SeparatorLine::Draw(BRect) +{ + BRect bounds(Bounds()); + rgb_color hiliteColor = tint_color(ViewColor(), 1.5f); + + bool vertical = (bounds.left > bounds.right - 3); + BeginLineArray(2); + if (vertical) { + AddLine(bounds.LeftTop(), bounds.LeftBottom(), hiliteColor); + AddLine(bounds.LeftTop() + BPoint(1, 0), bounds.LeftBottom() + BPoint(1, 0), kWhite); + } else { + AddLine(bounds.LeftTop(), bounds.RightTop(), hiliteColor); + AddLine(bounds.LeftTop() + BPoint(0, 1), bounds.RightTop() + BPoint(0, 1), kWhite); + } + EndLineArray(); +} + +void +HexDump(const void *buf, int32 length) +{ + const int32 kBytesPerLine = 16; + int32 offset; + unsigned char *buffer = (unsigned char *)buf; + + for (offset = 0; ; offset += kBytesPerLine, buffer += kBytesPerLine) { + int32 remain = length; + int32 index; + + printf( "0x%06x: ", (int)offset); + + for (index = 0; index < kBytesPerLine; index++) { + + if (remain-- > 0) + printf("%02x%c", buffer[index], remain > 0 ? ',' : ' '); + else + printf(" "); + } + + remain = length; + printf(" \'"); + for (index = 0; index < kBytesPerLine; index++) { + + if (remain-- > 0) + printf("%c", buffer[index] > ' ' ? buffer[index] : '.'); + else + printf(" "); + } + printf("\'\n"); + + length -= kBytesPerLine; + if (length <= 0) + break; + + } + fflush(stdout); +} + +void +EnableNamedMenuItem(BMenu *menu, const char *itemName, bool on) +{ + BMenuItem *item = menu->FindItem(itemName); + if (item) + item->SetEnabled(on); +} + +void +MarkNamedMenuItem(BMenu *menu, const char *itemName, bool on) +{ + BMenuItem *item = menu->FindItem(itemName); + if (item) + item->SetMarked(on); +} + +void +EnableNamedMenuItem(BMenu *menu, uint32 commandName, bool on) +{ + BMenuItem *item = menu->FindItem(commandName); + if (item) + item->SetEnabled(on); +} + +void +MarkNamedMenuItem(BMenu *menu, uint32 commandName, bool on) +{ + BMenuItem *item = menu->FindItem(commandName); + if (item) + item->SetMarked(on); +} + +void +DeleteSubmenu(BMenuItem *submenuItem) +{ + if (!submenuItem) + return; + + BMenu *menu = submenuItem->Submenu(); + if (!menu) + return; + + for (;;) { + BMenuItem *item = menu->RemoveItem((int32)0); + if (!item) + return; + + delete item; + } +} + +status_t +GetAppSignatureFromAttr(BFile *file, char *result) +{ + // This call is a performance improvement that + // avoids using the BAppFileInfo API when retrieving the + // app signature -- the call is expensive because by default + // the resource fork is scanned to read the attribute + +#ifdef B_APP_FILE_INFO_IS_FAST + + BAppFileInfo appFileInfo(file); + return appFileInfo.GetSignature(result); + +#else + + ssize_t readResult = file->ReadAttr(kAttrAppSignature, B_MIME_STRING_TYPE, + 0, result, B_MIME_TYPE_LENGTH); + + if (readResult <= 0) + return (status_t)readResult; + + return B_OK; + +#endif +} + +status_t +GetAppIconFromAttr(BFile *file, BBitmap *result, icon_size size) +{ + // This call is a performance improvement that + // avoids using the BAppFileInfo API when retrieving the + // app icons -- the call is expensive because by default + // the resource fork is scanned to read the icons + +#ifdef B_APP_FILE_INFO_IS_FAST + + BAppFileInfo appFileInfo(file); + return appFileInfo.GetIcon(result, size); + +#else + + const char *attrName = size == B_LARGE_ICON ? kAttrLargeIcon : kAttrMiniIcon; + uint32 type = size == B_LARGE_ICON ? LARGE_ICON_TYPE : MINI_ICON_TYPE; + char buffer[1024]; + + attr_info ainfo; + status_t err = err = file->GetAttrInfo(attrName, &ainfo); + if (err) + return err; + + ssize_t readResult = file->ReadAttr(attrName, type, 0, buffer, (size_t)ainfo.size); + + if (readResult <= 0) + return (status_t)readResult; + + result->SetBits(buffer, result->BitsLength(), 0, B_COLOR_8_BIT); + + return B_OK; + +#endif +} + +status_t +GetFileIconFromAttr(BNode *file, BBitmap *result, icon_size size) +{ + BNodeInfo fileInfo(file); + return fileInfo.GetIcon(result, size); +} + +void +PrintToStream(rgb_color color) +{ + printf("r:%x, g:%x, b:%x, a:%x\n", + color.red, color.green, color.blue, color.alpha); +} + + +extern BMenuItem * +EachMenuItem(BMenu *menu, bool recursive, BMenuItem *(*func)(BMenuItem *)) +{ + int32 count = menu->CountItems(); + for (int32 index = 0; index < count; index++) { + BMenuItem *item = menu->ItemAt(index); + BMenuItem *result = (func)(item); + if (result) + return result; + if (recursive) { + BMenu *submenu = menu->SubmenuAt(index); + if (submenu) + return EachMenuItem(submenu, true, func); + } + } + + return NULL; +} + +extern const BMenuItem * +EachMenuItem(const BMenu *menu, bool recursive, BMenuItem *(*func)(const BMenuItem *)) +{ + int32 count = menu->CountItems(); + for (int32 index = 0; index < count; index++) { + BMenuItem *item = menu->ItemAt(index); + BMenuItem *result = (func)(item); + if (result) + return result; + if (recursive) { + BMenu *submenu = menu->SubmenuAt(index); + if (submenu) + return EachMenuItem(submenu, true, func); + } + } + + return NULL; +} + +PositionPassingMenuItem::PositionPassingMenuItem(const char *title, + BMessage *message, char shortcut, uint32 modifiers) + : BMenuItem(title, message, shortcut, modifiers) +{ +} + +PositionPassingMenuItem::PositionPassingMenuItem(BMenu *menu, + BMessage *message) + : BMenuItem(menu, message) +{ +} + +status_t +PositionPassingMenuItem::Invoke(BMessage *message) +{ + if (!Menu()) + return B_ERROR; + + if (!IsEnabled()) + return B_ERROR; + + if (!message) + message = Message(); + + if (!message) + return B_BAD_VALUE; + + BMessage clone(*message); + clone.AddInt32("index", Menu()->IndexOf(this)); + clone.AddInt64("when", system_time()); + clone.AddPointer("source", this); + + // embed the invoke location of the menu so that we can create + // a new folder, etc. on the spot + BMenu *menu = Menu(); + + for (;;) { + if (!menu->Supermenu()) + break; + menu = menu->Supermenu(); + } + + // use the window position only, if the item was invoked from the menu + // menu->Window() points to the window the item was invoked from + if (dynamic_cast(menu->Window()) == NULL) + { + LooperAutoLocker lock(menu); + if (lock.IsLocked()) { + BPoint invokeOrigin(menu->Window()->Frame().LeftTop()); + clone.AddPoint("be:invoke_origin", invokeOrigin); + } + } + + return BInvoker::Invoke(&clone); +} + +bool +BootedInSafeMode() +{ + const char *safeMode = getenv("SAFEMODE"); + return (safeMode && strcmp(safeMode, "yes") == 0); +} + + +void +_ThrowOnError(status_t error, const char *DEBUG_ONLY(file), int32 DEBUG_ONLY(line)) +{ + if (error != B_OK) { + PRINT(("failing %s at %s:%d\n", strerror(error), file, line)); + throw error; + } +} + +void +_ThrowIfNotSize(ssize_t size, const char *DEBUG_ONLY(file), int32 DEBUG_ONLY(line)) +{ + if (size < B_OK) { + PRINT(("failing %s at %s:%d\n", strerror(size), file, line)); + throw (status_t)size; + } +} + +void +_ThrowOnError(status_t error, const char *DEBUG_ONLY(debugString), + const char *DEBUG_ONLY(file), int32 DEBUG_ONLY(line)) +{ + if (error != B_OK) { + PRINT(("failing %s, %s at %s:%d\n", debugString, strerror(error), file, line)); + throw error; + } +} + +} // namespace BPrivate diff --git a/src/kits/tracker/Utilities.h b/src/kits/tracker/Utilities.h new file mode 100644 index 0000000000..4e85167ab4 --- /dev/null +++ b/src/kits/tracker/Utilities.h @@ -0,0 +1,582 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef _UTILITIES_H +#define _UTILITIES_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +class BMessage; +class BVolume; +class BBitmap; +class BTextView; +class BView; + +namespace BPrivate { + +class Benaphore; + +// global variables +extern const rgb_color kBlack; +extern const rgb_color kWhite; + +const int64 kHalfKBSize = 512; +const int64 kKBSize = 1024; +const int64 kMBSize = 1048576; +const int64 kGBSize = 1073741824; +const int64 kTBSize = kGBSize * kKBSize; + +const int32 kMiniIconSeparator = 3; + +const color_space kDefaultIconDepth = B_COLOR_8_BIT; + +// misc typedefs, constants and structs + +// PoseInfo is the structure that gets saved as attributes for every node on +// disk, defining the node's position and visibility +class PoseInfo { + public: + static void EndianSwap(void *castToThis); + void PrintToStream(); + + bool fInvisible; + ino_t fInitedDirectory; + // for a location to be valid, fInitedDirectory has to contain the inode + // of the items parent directory + // This makes it impossible to for instance zip up files and extract + // them in the same location. This should probably be reworked -- Tracker + // could say strip the file location attributes when dropping files into + // a closed folder + BPoint fLocation; +}; + +// extends PoseInfo adding workspace support; used for desktop +// poses only +class ExtendedPoseInfo { + public: + size_t Size() const; + static size_t Size(int32); + size_t SizeWithHeadroom() const; + static size_t SizeWithHeadroom(size_t); + bool HasLocationForFrame(BRect) const; + BPoint LocationForFrame(BRect) const; + bool SetLocationForFrame(BPoint, BRect); + + static void EndianSwap(void *castToThis); + void PrintToStream(); + + uint32 fWorkspaces; + bool fInvisible; + bool fShowFromBootOnly; + bool fReservedBool1; + bool fReservedBool2; + int32 fReservedInt1; + int32 fReservedInt2; + int32 fReservedInt3; + int32 fReservedInt4; + int32 fReservedInt5; + + int32 fNumFrames; + struct FrameLocation { + BPoint fLocation; + BRect fFrame; + uint32 fWorkspaces; + }; + + FrameLocation fLocations[0]; +}; + +// misc functions +void DisallowMetaKeys(BTextView *); +void DisallowFilenameKeys(BTextView *); + +bool ValidateStream(BMallocIO *, uint32, int32 version); + +uint32 HashString(const char *string, uint32 seed); +uint32 AttrHashString(const char *string, uint32 type); + + +class OffscreenBitmap { + // a utility class for setting up offscreen bitmaps + public: + OffscreenBitmap(BRect bounds); + OffscreenBitmap(); + ~OffscreenBitmap(); + + BView *BeginUsing(BRect bounds); + void DoneUsing(); + BBitmap *Bitmap() const; + // blit this to your view when you are done rendering + BView *View() const; + // use this to render your image + + private: + void NewBitmap(BRect frame); + BBitmap *fBitmap; +}; + + +// bitmap functions +extern void FadeRGBA32Horizontal(uint32 *bits, int32 width, int32 height, int32 from, int32 to); +extern void FadeRGBA32Vertical(uint32 *bits, int32 width, int32 height, int32 from, int32 to); + + +class FlickerFreeStringView : public BStringView { + // Adds support for offscreen bitmap drawing for string views that update often + // this would be better implemented as an option of BStringView + public: + FlickerFreeStringView(BRect bounds, const char *name, + const char *text, uint32 resizeFlags = B_FOLLOW_LEFT | B_FOLLOW_TOP, + uint32 flags = B_WILL_DRAW); + FlickerFreeStringView(BRect bounds, const char *name, + const char *text, BBitmap *existingOffscreen, + uint32 resizeFlags = B_FOLLOW_LEFT | B_FOLLOW_TOP, + uint32 flags = B_WILL_DRAW); + virtual ~FlickerFreeStringView(); + virtual void Draw(BRect); + virtual void AttachedToWindow(); + virtual void SetViewColor(rgb_color); + virtual void SetLowColor(rgb_color); + + private: + OffscreenBitmap *fBitmap; + rgb_color fViewColor; + rgb_color fLowColor; + BBitmap *fOrigBitmap; + + typedef BStringView _inherited; +}; + + +class DraggableIcon : public BView { + // used to determine a save location for a file + public: + DraggableIcon(BRect, const char *, const char *mimeType, icon_size, + const BMessage *, BMessenger, + uint32 resizeFlags = B_FOLLOW_LEFT | B_FOLLOW_TOP, + uint32 flags = B_WILL_DRAW); + virtual ~DraggableIcon(); + + static BRect PreferredRect(BPoint offset, icon_size); + void SetTarget(BMessenger); + + protected: + virtual void AttachedToWindow(); + virtual void MouseDown(BPoint); + virtual void Draw(BRect); + + virtual bool DragStarted(BMessage *dragMessage); + + protected: + BBitmap *fBitmap; + BMessage fMessage; + BMessenger fTarget; +}; + + +class PositionPassingMenuItem : public BMenuItem { + public: + PositionPassingMenuItem(const char *title, BMessage *, char shortcut = 0, + uint32 modifiers = 0); + + PositionPassingMenuItem(BMenu *, BMessage *); + + protected: + virtual status_t Invoke(BMessage * = 0); + // appends the invoke location for NewFolder, etc. to use + + private: + typedef BMenuItem _inherited; +}; + + +class Benaphore { + // aka benaphore + public: + Benaphore(const char *name = "Light Lock") + : fSemaphore(create_sem(0, name)), + fCount(1) + { + } + + ~Benaphore() + { + delete_sem(fSemaphore); + } + + bool Lock() + { + if (atomic_add(&fCount, -1) <= 0) + return acquire_sem(fSemaphore) == B_OK; + + return true; + } + + void Unlock() + { + if (atomic_add(&fCount, 1) < 0) + release_sem(fSemaphore); + } + + bool IsLocked() const + { + return fCount <= 0; + } + + private: + sem_id fSemaphore; + int32 fCount; +}; + + +class SeparatorLine : public BView { + public: + SeparatorLine(BPoint , float , bool vertical, const char *name = ""); + virtual void Draw(BRect bounds); +}; + + +class TitledSeparatorItem : public BMenuItem { + public: + TitledSeparatorItem(const char *); + virtual ~TitledSeparatorItem(); + + virtual void SetEnabled(bool state); + + protected: + virtual void GetContentSize(float *width, float *height); + virtual void Draw(); + + private: + typedef BMenuItem _inherited; +}; + + +class LooperAutoLocker { + public: + LooperAutoLocker(BHandler *handler) + : fHandler(handler), + fHasLock(handler->LockLooper()) + { + } + + ~LooperAutoLocker() + { + if (fHasLock) + fHandler->UnlockLooper(); + } + + bool operator!() const + { + return !fHasLock; + } + + bool IsLocked() const + { + return fHasLock; + } + + private: + BHandler *fHandler; + bool fHasLock; +}; + + +class MessengerAutoLocker { + // move this into AutoLock.h + public: + MessengerAutoLocker(BMessenger *messenger) + : fMessenger(messenger), + fHasLock(messenger->LockTarget()) + { } + + ~MessengerAutoLocker() + { + Unlock(); + } + + bool operator!() const + { + return !fHasLock; + } + + bool IsLocked() const + { + return fHasLock; + } + + void Unlock() + { + if (fHasLock) { + BLooper *looper; + fMessenger->Target(&looper); + if (looper) + looper->Unlock(); + fHasLock = false; + } + } + + private: + BMessenger *fMessenger; + bool fHasLock; +}; + + +class ShortcutFilter : public BMessageFilter { + public: + ShortcutFilter(uint32 shortcutKey, uint32 shortcutModifier, + uint32 shortcutWhat, BHandler *target); + + protected: + filter_result Filter(BMessage *, BHandler **); + + private: + uint32 fShortcutKey; + uint32 fShortcutModifier; + uint32 fShortcutWhat; + BHandler *fTarget; +}; + +// iterates over all the refs in a message +entry_ref *EachEntryRef(BMessage *, entry_ref *(*)(entry_ref *, void *), + void *passThru = 0); +const entry_ref *EachEntryRef(const BMessage *, + const entry_ref *(*)(const entry_ref *, void *), void *passThru = 0); + +entry_ref *EachEntryRef(BMessage *, entry_ref *(*)(entry_ref *, void *), + void *passThru, int32 maxCount); +const entry_ref *EachEntryRef(const BMessage *, + const entry_ref *(*)(const entry_ref *, void *), void *passThru, int32 maxCount); + + +bool ContainsEntryRef(const BMessage *, const entry_ref *); +int32 CountRefs(const BMessage *); + +BMenuItem *EachMenuItem(BMenu *menu, bool recursive, BMenuItem *(*func)(BMenuItem *)); +const BMenuItem *EachMenuItem(const BMenu *menu, bool recursive, + BMenuItem *(*func)(const BMenuItem *)); + +int64 StringToScalar(const char *text); + // string to num, understands kB, MB, etc. + +// misc calls +void EmbedUniqueVolumeInfo(BMessage *, const BVolume *); +status_t MatchArchivedVolume(BVolume *, const BMessage *, int32 index = 0); +void TruncateLeaf(BString *string); + +void StringFromStream(BString *, BMallocIO *, bool endianSwap = false); +void StringToStream(const BString *, BMallocIO *); +int32 ArchiveSize(const BString *); + +extern void EnableNamedMenuItem(BMenu *menu, const char *itemName, bool on); +extern void MarkNamedMenuItem(BMenu *menu, const char *itemName, bool on); +extern void EnableNamedMenuItem(BMenu *menu, uint32 commandName, bool on); +extern void MarkNamedMenuItem(BMenu *menu, uint32 commandName, bool on); +extern void DeleteSubmenu(BMenuItem *submenuItem); + +extern bool BootedInSafeMode(); + +// Now is in kits +#if B_BEOS_VERSION <= B_BEOS_VERSION_MAUI + +// Should be in kits +bool operator==(const rgb_color &, const rgb_color &); +bool operator!=(const rgb_color &, const rgb_color &); + +#endif + +inline rgb_color +Color(int32 r, int32 g, int32 b, int32 alpha = 255) +{ + rgb_color result; + result.red = (uchar)r; + result.green = (uchar)g; + result.blue = (uchar)b; + result.alpha = (uchar)alpha; + + return result; +} + +void PrintToStream(rgb_color color); + +template +void +ThrowOnInitCheckError(InitCheckable *item) +{ + if (!item) + throw B_ERROR; + status_t error = item->InitCheck(); + if (error != B_OK) + throw error; +} + +#if DEBUG +#define ThrowOnError(error) _ThrowOnError(error, __FILE__, __LINE__) +#define ThrowIfNotSize(error) _ThrowIfNotSize(error, __FILE__, __LINE__) +#define ThrowOnErrorWithMessage(error, debugStr) _ThrowOnError(error, debugStr, __FILE__, __LINE__) +#else +#define ThrowOnError(x) _ThrowOnError(x, 0, 0) +#define ThrowIfNotSize(x) _ThrowIfNotSize(x, 0, 0) +#define ThrowOnErrorWithMessage(error, debugStr) _ThrowOnError(error, debugStr, __FILE__, __LINE__) +#endif + +void _ThrowOnError(status_t, const char *, int32); +void _ThrowIfNotSize(ssize_t, const char *, int32); +void _ThrowOnError(status_t, const char *debugStr, const char *, int32); + +// stub calls that work around BAppFile info inefficiency +status_t GetAppSignatureFromAttr(BFile *, char *); +status_t GetAppIconFromAttr(BFile *, BBitmap *, icon_size); +status_t GetFileIconFromAttr(BNode *, BBitmap *, icon_size); + + +// debugging +void HexDump(const void *buffer, int32 length); + +#if xDEBUG + +inline void +PrintRefToStream(const entry_ref *ref, const char *trailer = "\n") +{ + if (!ref) { + PRINT(("NULL entry_ref%s", trailer)); + return; + } + BPath path; + BEntry entry(ref); + entry.GetPath(&path); + PRINT(("%s%s", path.Path(), trailer)); +} + +inline void +PrintEntryToStream(const BEntry *entry, const char *trailer = "\n") +{ + if (!entry) { + PRINT(("NULL entry%s", trailer)); + return; + } + BPath path; + entry->GetPath(&path); + PRINT(("%s%s", path.Path(), trailer)); +} + +inline void +PrintDirToStream(const BDirectory *dir, const char *trailer = "\n") +{ + if (!dir) { + PRINT(("NULL entry_ref%s", trailer)); + return; + } + BPath path; + BEntry entry; + dir->GetEntry(&entry); + entry.GetPath(&path); + PRINT(("%s%s", path.Path(), trailer)); +} + +#else + +inline void PrintRefToStream(const entry_ref *, const char * = 0) {} +inline void PrintEntryToStream(const BEntry *, const char * = 0) {} +inline void PrintDirToStream(const BDirectory *, const char * = 0) {} + +#endif + +#ifdef xDEBUG + + extern FILE *logFile; + + inline void PrintToLogFile(const char *fmt, ...) + { + va_list ap; + va_start(ap, fmt); + vfprintf(logFile, fmt, ap); + va_end(ap); + } + + #define WRITELOG(_ARGS_) \ + if (logFile == 0) \ + logFile = fopen("/var/log/tracker.log", "a+"); \ + if (logFile != 0) { \ + thread_info info; \ + get_thread_info(find_thread(NULL), &info); \ + PrintToLogFile("[t %Ld] \"%s\" (%s:%i) ", system_time(), \ + info.name, __FILE__, __LINE__); \ + PrintToLogFile _ARGS_; \ + PrintToLogFile("\n"); \ + fflush(logFile); \ + } + +#else + + #define WRITELOG(_ARGS_) + +#endif + +// fancy casting macros + +template +inline NewType assert_cast(OldType castedPointer) { + ASSERT(dynamic_cast(castedPointer) != NULL); + return static_cast(castedPointer); +} + +// B_SWAP_INT32 have broken signedness, simple cover calls to fix that +// should fix up in ByteOrder.h + +inline int32 SwapInt32(int32 value) { return (int32)B_SWAP_INT32((uint32)value); } +inline uint32 SwapUInt32(uint32 value) { return B_SWAP_INT32(value); } +inline int64 SwapInt64(int64 value) { return (int64)B_SWAP_INT64((uint64)value); } +inline uint64 SwapUInt64(uint64 value) { return B_SWAP_INT64(value); } + + +} // namespace BPrivate + +#endif diff --git a/src/kits/tracker/ViewState.cpp b/src/kits/tracker/ViewState.cpp new file mode 100644 index 0000000000..9b2af6d7ee --- /dev/null +++ b/src/kits/tracker/ViewState.cpp @@ -0,0 +1,400 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include +#include + +#include +#include +#include + +#include "Attributes.h" +#include "Commands.h" +#include "PoseView.h" +#include "Utilities.h" +#include "ViewState.h" + +const char *kColumnVersionName = "BColumn:version"; +const char *kColumnTitleName = "BColumn:fTitle"; +const char *kColumnOffsetName = "BColumn:fOffset"; +const char *kColumnWidthName = "BColumn:fWidth"; +const char *kColumnAlignmentName = "BColumn:fAlignment"; +const char *kColumnAttrName = "BColumn:fAttrName"; +const char *kColumnAttrHashName = "BColumn:fAttrHash"; +const char *kColumnAttrTypeName = "BColumn:fAttrType"; +const char *kColumnStatFieldName = "BColumn:fStatField"; +const char *kColumnEditableName = "BColumn:fEditable"; + +BColumn::BColumn(const char *title, float offset, float width, alignment a, + const char *attributeName, uint32 attr_type, bool stat_field, + bool editable) + : fTitle(title), + fAttrName(attributeName) +{ + fOffset = offset; + fWidth = width; + fAlignment = a; + fAttrHash = AttrHashString(attributeName, attr_type); + fAttrType = attr_type; + fStatField = stat_field; + fEditable = editable; +} + +BColumn::~BColumn() +{ +} + +BColumn::BColumn(BMallocIO *stream, bool endianSwap) +{ + StringFromStream(&fTitle, stream, endianSwap); + stream->Read(&fOffset, sizeof(float)); + stream->Read(&fWidth, sizeof(float)); + stream->Read(&fAlignment, sizeof(alignment)); + StringFromStream(&fAttrName, stream, endianSwap); + stream->Read(&fAttrHash, sizeof(uint32)); + stream->Read(&fAttrType, sizeof(uint32)); + stream->Read(&fStatField, sizeof(bool)); + stream->Read(&fEditable, sizeof(bool)); + + if (endianSwap) { + PRINT(("endian swapping column\n")); + fOffset = B_SWAP_FLOAT(fOffset); + fWidth = B_SWAP_FLOAT(fWidth); + STATIC_ASSERT(sizeof(BColumn::fAlignment) == sizeof(int32)); + fAlignment = (alignment)B_SWAP_INT32(fAlignment); + fAttrHash = B_SWAP_INT32(fAttrHash); + fAttrType = B_SWAP_INT32(fAttrType); + } +} + +BColumn::BColumn(const BMessage &message, int32 index) +{ + message.FindString(kColumnTitleName, index, &fTitle); + message.FindFloat(kColumnOffsetName, index, &fOffset); + message.FindFloat(kColumnWidthName, index, &fWidth); + message.FindInt32(kColumnAlignmentName, index, (int32 *)&fAlignment); + message.FindString(kColumnAttrName, index, &fAttrName); + message.FindInt32(kColumnAttrHashName, index, (int32 *)&fAttrHash); + message.FindInt32(kColumnAttrTypeName, index, (int32 *)&fAttrType); + message.FindBool(kColumnStatFieldName, index, &fStatField); + message.FindBool(kColumnEditableName, index, &fEditable); +} + +BColumn * +BColumn::InstantiateFromStream(BMallocIO *stream, bool endianSwap) +{ + // compare stream header in canonical form + uint32 key = AttrHashString("BColumn", B_OBJECT_TYPE); + int32 version = kColumnStateArchiveVersion; + + + if (endianSwap) { + key = SwapUInt32(key); + version = SwapInt32(version); + } + + // PRINT(("validating key %x, version %d\n", key, version)); + if (!ValidateStream(stream, key, version)) + return 0; + + // PRINT(("instantiating column, %s\n", endianSwap ? "endian swapping," : "")); + BColumn *result = new BColumn(stream, endianSwap); + + // sanity-check the resulting column + if (result->fTitle.Length() > 500 + || result->fOffset < 0 + || result->fOffset > 10000 + || result->fWidth < 0 + || result->fWidth > 10000 + || (int32)result->fAlignment < B_ALIGN_LEFT + || (int32)result->fAlignment > B_ALIGN_CENTER + || result->fAttrName.Length() > 500) { + PRINT(("column data not valid\n")); + delete result; + return 0; + } +#if DEBUG + else if (endianSwap) + PRINT(("Instantiated foreign column ok\n")); +#endif + + return result; +} + +BColumn * +BColumn::InstantiateFromMessage(const BMessage &message, int32 index) +{ + int32 version = kColumnStateArchiveVersion; + int32 messageVersion; + + if (message.FindInt32(kColumnVersionName, index, &messageVersion) != B_OK) + return NULL; + + if (version != messageVersion) + return NULL; + + BColumn *result = new BColumn(message, index); + + // sanity-check the resulting column + if (result->fTitle.Length() > 500 + || result->fOffset < 0 + || result->fOffset > 10000 + || result->fWidth < 0 + || result->fWidth > 10000 + || (int32)result->fAlignment < B_ALIGN_LEFT + || (int32)result->fAlignment > B_ALIGN_CENTER + || result->fAttrName.Length() > 500) { + PRINT(("column data not valid\n")); + delete result; + return NULL; + } + return result; +} + +void +BColumn::ArchiveToStream(BMallocIO *stream) const +{ + // write class identifier and version info + uint32 key = AttrHashString("BColumn", B_OBJECT_TYPE); + stream->Write(&key, sizeof(uint32)); + int32 version = kColumnStateArchiveVersion; + stream->Write(&version, sizeof(int32)); + + // PRINT(("ArchiveToStream column, key %x, version %d\n", key, version)); + + StringToStream(&fTitle, stream); + stream->Write(&fOffset, sizeof(float)); + stream->Write(&fWidth, sizeof(float)); + stream->Write(&fAlignment, sizeof(alignment)); + StringToStream(&fAttrName, stream); + stream->Write(&fAttrHash, sizeof(uint32)); + stream->Write(&fAttrType, sizeof(uint32)); + stream->Write(&fStatField, sizeof(bool)); + stream->Write(&fEditable, sizeof(bool)); +} + +void +BColumn::ArchiveToMessage(BMessage &message) const +{ + message.AddInt32(kColumnVersionName, kColumnStateArchiveVersion); + + message.AddString(kColumnTitleName, fTitle); + message.AddFloat(kColumnOffsetName, fOffset); + message.AddFloat(kColumnWidthName, fWidth); + message.AddInt32(kColumnAlignmentName, fAlignment); + message.AddString(kColumnAttrName, fAttrName); + message.AddInt32(kColumnAttrHashName, static_cast(fAttrHash)); + message.AddInt32(kColumnAttrTypeName, static_cast(fAttrType)); + message.AddBool(kColumnStatFieldName, fStatField); + message.AddBool(kColumnEditableName, fEditable); +} + +const char *kViewStateVersionName = "ViewState:version"; +const char *kViewStateViewModeName = "ViewState:fViewMode"; +const char *kViewStateLastIconModeName = "ViewState:fLastIconMode"; +const char *kViewStateListOriginName = "ViewState:fListOrigin"; +const char *kViewStateIconOriginName = "ViewState:fIconOrigin"; +const char *kViewStatePrimarySortAttrName = "ViewState:fPrimarySortAttr"; +const char *kViewStatePrimarySortTypeName = "ViewState:fPrimarySortType"; +const char *kViewStateSecondarySortAttrName = "ViewState:fSecondarySortAttr"; +const char *kViewStateSecondarySortTypeName = "ViewState:fSecondarySortType"; +const char *kViewStateReverseSortName = "ViewState:fReverseSort"; + +BViewState::BViewState() +{ + fViewMode = kListMode; + fLastIconMode = 0; + fListOrigin.Set(0, 0); + fIconOrigin.Set(0, 0); + fPrimarySortAttr = AttrHashString(kAttrStatName, B_STRING_TYPE); + fPrimarySortType = B_STRING_TYPE; + fSecondarySortAttr = 0; + fSecondarySortType = 0; + fReverseSort = false; + fStateNeedsSaving = false; +} + +BViewState::BViewState(BMallocIO *stream, bool endianSwap) +{ + stream->Read(&fViewMode, sizeof(uint32)); + stream->Read(&fLastIconMode, sizeof(uint32)); + stream->Read(&fListOrigin, sizeof(BPoint)); + stream->Read(&fIconOrigin, sizeof(BPoint)); + stream->Read(&fPrimarySortAttr, sizeof(uint32)); + stream->Read(&fPrimarySortType, sizeof(uint32)); + stream->Read(&fSecondarySortAttr, sizeof(uint32)); + stream->Read(&fSecondarySortType, sizeof(uint32)); + stream->Read(&fReverseSort, sizeof(bool)); + + if (endianSwap) { + PRINT(("endian swapping view state\n")); + fViewMode = B_SWAP_INT32(fViewMode); + fLastIconMode = B_SWAP_INT32(fLastIconMode); + swap_data(B_POINT_TYPE, &fListOrigin, sizeof(fListOrigin), B_SWAP_ALWAYS); + swap_data(B_POINT_TYPE, &fIconOrigin, sizeof(fIconOrigin), B_SWAP_ALWAYS); + fPrimarySortAttr = B_SWAP_INT32(fPrimarySortAttr); + fSecondarySortAttr = B_SWAP_INT32(fSecondarySortAttr); + fPrimarySortType = B_SWAP_INT32(fPrimarySortType); + fSecondarySortType = B_SWAP_INT32(fSecondarySortType); + } + fStateNeedsSaving = false; +} + +BViewState::BViewState(const BMessage &message) +{ + message.FindInt32(kViewStateViewModeName, (int32 *)&fViewMode); + message.FindInt32(kViewStateLastIconModeName, (int32 *)&fLastIconMode); + message.FindPoint(kViewStateListOriginName, &fListOrigin); + message.FindPoint(kViewStateIconOriginName, &fIconOrigin); + message.FindInt32(kViewStatePrimarySortAttrName, (int32 *)&fPrimarySortAttr); + message.FindInt32(kViewStatePrimarySortTypeName, (int32 *)&fPrimarySortType); + message.FindInt32(kViewStateSecondarySortAttrName, (int32 *)&fSecondarySortAttr); + message.FindInt32(kViewStateSecondarySortTypeName, (int32 *)&fSecondarySortType); + message.FindBool(kViewStateReverseSortName, &fReverseSort); + + fStateNeedsSaving = false; +} + +void +BViewState::ArchiveToStream(BMallocIO *stream) const +{ + // write class identifier and verison info + uint32 key = AttrHashString("BViewState", B_OBJECT_TYPE); + stream->Write(&key, sizeof(key)); + int32 version = kViewStateArchiveVersion; + stream->Write(&version, sizeof(version)); + + stream->Write(&fViewMode, sizeof(uint32)); + stream->Write(&fLastIconMode, sizeof(uint32)); + stream->Write(&fListOrigin, sizeof(BPoint)); + stream->Write(&fIconOrigin, sizeof(BPoint)); + stream->Write(&fPrimarySortAttr, sizeof(uint32)); + stream->Write(&fPrimarySortType, sizeof(uint32)); + stream->Write(&fSecondarySortAttr, sizeof(uint32)); + stream->Write(&fSecondarySortType, sizeof(uint32)); + stream->Write(&fReverseSort, sizeof(bool)); +} + + +void +BViewState::ArchiveToMessage(BMessage &message) const +{ + message.AddInt32(kViewStateVersionName, kViewStateArchiveVersion); + + message.AddInt32(kViewStateViewModeName, static_cast(fViewMode)); + message.AddInt32(kViewStateLastIconModeName, static_cast(fLastIconMode)); + message.AddPoint(kViewStateListOriginName, fListOrigin); + message.AddPoint(kViewStateIconOriginName, fIconOrigin); + message.AddInt32(kViewStatePrimarySortAttrName, static_cast(fPrimarySortAttr)); + message.AddInt32(kViewStatePrimarySortTypeName, static_cast(fPrimarySortType)); + message.AddInt32(kViewStateSecondarySortAttrName, static_cast(fSecondarySortAttr)); + message.AddInt32(kViewStateSecondarySortTypeName, static_cast(fSecondarySortType)); + message.AddBool(kViewStateReverseSortName, fReverseSort); +} + + + +BViewState * +BViewState::InstantiateFromStream(BMallocIO *stream, bool endianSwap) +{ + // compare stream header in canonical form + uint32 key = AttrHashString("BViewState", B_OBJECT_TYPE); + int32 version = kViewStateArchiveVersion; + + if (endianSwap) { + key = SwapUInt32(key); + version = SwapInt32(version); + } + + if (!ValidateStream(stream, key, version)) + return NULL; + + BViewState *result = new BViewState(stream, endianSwap); + + // do a sanity check here + if ((result->fViewMode != kListMode + && result->fViewMode != kIconMode + && result->fViewMode != kMiniIconMode + && result->fViewMode != 0) + || (result->fLastIconMode != kListMode + && result->fLastIconMode != kIconMode + && result->fLastIconMode != kMiniIconMode + && result->fLastIconMode != 0)) { + + PRINT(("Bad data instantiating ViewState, view mode %x, lastIconMode %x\n", + result->fViewMode, result->fLastIconMode)); + + delete result; + return NULL; + } +#if DEBUG + else if (endianSwap) + PRINT(("Instantiated foreign view state ok\n")); +#endif + return result; +} + +BViewState * +BViewState::InstantiateFromMessage(const BMessage &message) +{ + int32 version = kViewStateArchiveVersion; + + int32 messageVersion; + + if (message.FindInt32(kViewStateVersionName, &messageVersion) != B_OK) + return NULL; + + if (version != messageVersion) + return NULL; + + BViewState *result = new BViewState(message); + + // do a sanity check here + if ((result->fViewMode != kListMode + && result->fViewMode != kIconMode + && result->fViewMode != kMiniIconMode + && result->fViewMode != 0) + || (result->fLastIconMode != kListMode + && result->fLastIconMode != kIconMode + && result->fLastIconMode != kMiniIconMode + && result->fLastIconMode != 0)) { + + PRINT(("Bad data instantiating ViewState, view mode %x, lastIconMode %x\n", + result->fViewMode, result->fLastIconMode)); + + delete result; + return NULL; + } + return result; +} diff --git a/src/kits/tracker/ViewState.h b/src/kits/tracker/ViewState.h new file mode 100644 index 0000000000..9b290fcc66 --- /dev/null +++ b/src/kits/tracker/ViewState.h @@ -0,0 +1,354 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef _VIEW_STATE_H +#define _VIEW_STATE_H + +#include +#include + +namespace BPrivate { + +const int32 kColumnStateArchiveVersion = 21; + // bump version when layout or size changes + +class BColumn { +public: + BColumn(const char *title, float offset, float width, + alignment, const char *attributeName, uint32 attr_type, + bool stat_field, bool editable); + ~BColumn(); + + BColumn(BMallocIO *stream, bool endianSwap = false); + BColumn(const BMessage &, int32 index = 0); + static BColumn *InstantiateFromStream(BMallocIO *stream, bool endianSwap = false); + static BColumn *InstantiateFromMessage(const BMessage &, int32 index = 0); + void ArchiveToStream(BMallocIO *stream) const; + void ArchiveToMessage(BMessage &) const; + + const char *Title() const; + float Offset() const; + float Width() const; + alignment Alignment() const; + const char *AttrName() const; + uint32 AttrType() const; + uint32 AttrHash() const; + bool StatField() const; + bool Editable() const; + + void SetOffset(float); + void SetWidth(float); + +private: + BString fTitle; + float fOffset; + float fWidth; + alignment fAlignment; + BString fAttrName; + uint32 fAttrHash; + uint32 fAttrType; + bool fStatField; + bool fEditable; +}; + + +const int32 kViewStateArchiveVersion = 10; + // bump version when layout or size changes + +class BViewState { + public: + BViewState(); + + BViewState(BMallocIO *stream, bool endianSwap = false); + BViewState(const BMessage &message); + static BViewState *InstantiateFromStream(BMallocIO *stream, bool endianSwap = false); + static BViewState *InstantiateFromMessage(const BMessage &message); + void ArchiveToStream(BMallocIO *stream) const; + void ArchiveToMessage(BMessage &message) const; + + uint32 ViewMode() const; + uint32 LastIconMode() const; + BPoint ListOrigin() const; + BPoint IconOrigin() const; + uint32 PrimarySort() const; + uint32 SecondarySort() const; + uint32 PrimarySortType() const; + uint32 SecondarySortType() const; + bool ReverseSort() const; + + void SetViewMode(uint32); + void SetLastIconMode(uint32); + void SetListOrigin(BPoint); + void SetIconOrigin(BPoint); + void SetPrimarySort(uint32); + void SetSecondarySort(uint32); + void SetPrimarySortType(uint32); + void SetSecondarySortType(uint32); + void SetReverseSort(bool); + + bool StateNeedsSaving(); + void MarkSaved(); + +private: + uint32 fViewMode; + uint32 fLastIconMode; + BPoint fListOrigin; + BPoint fIconOrigin; + uint32 fPrimarySortAttr; + uint32 fSecondarySortAttr; + uint32 fPrimarySortType; + uint32 fSecondarySortType; + bool fReverseSort; + bool fStateNeedsSaving; +}; + +inline const char * +BColumn::Title() const +{ + return fTitle.String(); +} + +inline float +BColumn::Offset() const +{ + return fOffset; +} + +inline float +BColumn::Width() const +{ + return fWidth; +} + +inline alignment +BColumn::Alignment() const +{ + return fAlignment; +} + +inline const char * +BColumn::AttrName() const +{ + return fAttrName.String(); +} + +inline uint32 +BColumn::AttrHash() const +{ + return fAttrHash; +} + +inline uint32 +BColumn::AttrType() const +{ + return fAttrType; +} + +inline bool +BColumn::StatField() const +{ + return fStatField; +} + +inline bool +BColumn::Editable() const +{ + return fEditable; +} + +inline void +BColumn::SetWidth(float w) +{ + fWidth = w; +} + +inline void +BColumn::SetOffset(float o) +{ + fOffset = o; +} + +inline uint32 +BViewState::ViewMode() const +{ + return fViewMode; +} + +inline uint32 +BViewState::LastIconMode() const +{ + return fLastIconMode; +} + +inline BPoint +BViewState::ListOrigin() const +{ + return fListOrigin; +} + +inline BPoint +BViewState::IconOrigin() const +{ + return fIconOrigin; +} + +inline uint32 +BViewState::PrimarySort() const +{ + return fPrimarySortAttr; +} + +inline uint32 +BViewState::SecondarySort() const +{ + return fSecondarySortAttr; +} + +inline uint32 +BViewState::PrimarySortType() const +{ + return fPrimarySortType; +} + +inline uint32 +BViewState::SecondarySortType() const +{ + return fSecondarySortType; +} + +inline bool +BViewState::ReverseSort() const +{ + return fReverseSort; +} + +inline void +BViewState::SetViewMode(uint32 mode) +{ + if (mode != fViewMode) + fStateNeedsSaving = true; + + fViewMode = mode; +} + +inline void +BViewState::SetLastIconMode(uint32 mode) +{ + if (mode != fLastIconMode) + fStateNeedsSaving = true; + + fLastIconMode = mode; +} + +inline void +BViewState::SetListOrigin(BPoint newOrigin) +{ + if (newOrigin != fListOrigin) + fStateNeedsSaving = true; + + fListOrigin = newOrigin; +} + +inline void +BViewState::SetIconOrigin(BPoint newOrigin) +{ + if (newOrigin != fIconOrigin) + fStateNeedsSaving = true; + + fIconOrigin = newOrigin; +} + +inline void +BViewState::SetPrimarySort(uint32 attr) +{ + if (attr != fPrimarySortAttr) + fStateNeedsSaving = true; + + fPrimarySortAttr = attr; +} + +inline void +BViewState::SetSecondarySort(uint32 attr) +{ + if (attr != fSecondarySortAttr) + fStateNeedsSaving = true; + + fSecondarySortAttr = attr; +} + +inline void +BViewState::SetPrimarySortType(uint32 type) +{ + if (type != fPrimarySortType) + fStateNeedsSaving = true; + + fPrimarySortType = type; +} + +inline void +BViewState::SetSecondarySortType(uint32 type) +{ + if (type != fSecondarySortType) + fStateNeedsSaving = true; + + fSecondarySortType = type; +} + +inline void +BViewState::SetReverseSort(bool on) +{ + if (fReverseSort != on) + fStateNeedsSaving = true; + + fReverseSort = on; +} + +inline bool +BViewState::StateNeedsSaving() +{ + return fStateNeedsSaving; +} + +inline void +BViewState::MarkSaved() +{ + fStateNeedsSaving = false; +} + + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/VolumeWindow.cpp b/src/kits/tracker/VolumeWindow.cpp new file mode 100644 index 0000000000..4ddfe1e94c --- /dev/null +++ b/src/kits/tracker/VolumeWindow.cpp @@ -0,0 +1,147 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Commands.h" +#include "VolumeWindow.h" +#include "PoseView.h" +#include "MountMenu.h" + + +BVolumeWindow::BVolumeWindow(LockingList *windowList, uint32 openFlags) + : BContainerWindow(windowList, openFlags) +{ +} + + +void +BVolumeWindow::MenusBeginning() +{ + _inherited::MenusBeginning(); + + if (!fMenuBar) + return; + + BVolume boot; + BVolumeRoster().GetBootVolume(&boot); + + bool ejectableVolumeSelected = false; + + int32 count = PoseView()->SelectionList()->CountItems(); + for (int32 index = 0; index < count; index++) { + Model *model = PoseView()->SelectionList()->ItemAt(index)->TargetModel(); + if (model->IsVolume()) { + BVolume volume; + volume.SetTo(model->NodeRef()->device); + if (volume != boot) { + ejectableVolumeSelected = true; + break; + } + } + } + + BMenuItem *item = fMenuBar->FindItem("Unmount"); + if (item) + item->SetEnabled(ejectableVolumeSelected); +} + + +void +BVolumeWindow::AddFileMenu(BMenu *menu) +{ + menu->AddItem(new BMenuItem("Find"B_UTF8_ELLIPSIS, + new BMessage(kFindButton), 'F')); + menu->AddSeparatorItem(); + + menu->AddItem(new BMenuItem("Open", new BMessage(kOpenSelection), 'O')); + menu->AddItem(new BMenuItem("Get Info", new BMessage(kGetInfo), 'I')); + menu->AddItem(new BMenuItem("Edit Name", new BMessage(kEditItem), 'E')); + + BMenuItem *item = new BMenuItem("Unmount", new BMessage(kUnmountVolume), 'U'); + item->SetEnabled(false); + menu->AddItem(item); + + menu->AddItem(new BMenuItem("Mount Settings" B_UTF8_ELLIPSIS, + new BMessage(kRunAutomounterSettings))); + + menu->AddSeparatorItem(); + menu->AddItem(new BMenu(kAddOnsMenuName)); + menu->SetTargetForItems(PoseView()); +} + + +void +BVolumeWindow::AddWindowContextMenus(BMenu *menu) +{ + if (fPoseView != NULL && fPoseView->TargetModel() != NULL + && !fPoseView->TargetModel()->IsRoot()) { + _inherited::AddWindowContextMenus(menu); + return; + } + + menu->AddItem(new BMenuItem("Icon View", new BMessage(kIconMode))); + menu->AddItem(new BMenuItem("Mini Icon View", new BMessage(kMiniIconMode))); + menu->AddItem(new BMenuItem("List View", new BMessage(kListMode))); + menu->AddSeparatorItem(); + + BMenuItem *resizeItem = new BMenuItem("Resize to Fit",new BMessage(kResizeToFit), 'Y'); + menu->AddItem(resizeItem); + menu->AddItem(new BMenuItem("Clean Up", new BMessage(kCleanup), 'K')); + menu->AddItem(new BMenuItem("Select"B_UTF8_ELLIPSIS, new BMessage(kShowSelectionWindow), 'A', B_SHIFT_KEY)); + menu->AddItem(new BMenuItem("Select All", new BMessage(B_SELECT_ALL), 'A')); + menu->AddItem(new BMenuItem("Invert Selection", new BMessage(kInvertSelection), 'S')); + + BMenuItem *closeItem = new BMenuItem("Close",new BMessage(B_QUIT_REQUESTED), 'W'); + menu->AddItem(closeItem); + menu->AddSeparatorItem(); + + menu->AddItem(new MountMenu("Mount")); + menu->AddSeparatorItem(); + + menu->AddItem(new BMenu(kAddOnsMenuName)); + + // target items as needed + menu->SetTargetForItems(PoseView()); + closeItem->SetTarget(this); + resizeItem->SetTarget(this); +} + diff --git a/src/kits/tracker/VolumeWindow.h b/src/kits/tracker/VolumeWindow.h new file mode 100644 index 0000000000..d8f683995b --- /dev/null +++ b/src/kits/tracker/VolumeWindow.h @@ -0,0 +1,63 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef _VOLUME_WINDOW_H +#define _VOLUME_WINDOW_H + +#include "ContainerWindow.h" + +namespace BPrivate { + +class BVolumeWindow : public BContainerWindow { + // The volume window displays the virtual file system root with + // all mounted volumes. Does not show up unless the corresponding Tracker + // setting is enabled + public: + BVolumeWindow(LockingList *windowList, uint32 containerWindowFlags); + + protected: + virtual void AddFileMenu(BMenu *menu); + virtual void AddWindowContextMenus(BMenu *); + + virtual void MenusBeginning(); + + private: + typedef BContainerWindow _inherited; +}; + +} // namespace BPrivate + +using namespace BPrivate; + +#endif diff --git a/src/kits/tracker/WidgetAttributeText.cpp b/src/kits/tracker/WidgetAttributeText.cpp new file mode 100644 index 0000000000..adf6f29160 --- /dev/null +++ b/src/kits/tracker/WidgetAttributeText.cpp @@ -0,0 +1,1772 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Attributes.h" +#include "FindPanel.h" +#include "FSUndoRedo.h" +#include "FSUtils.h" +#include "Model.h" +#include "OpenWithWindow.h" +#include "MimeTypes.h" +#include "PoseView.h" +#include "SettingsViews.h" +#include "Utilities.h" +#include "ViewState.h" +#include "WidgetAttributeText.h" + + +template +float +TruncStringBase(BString *result, const char *str, int32 length, + const View *view, float width, uint32 truncMode = B_TRUNCATE_MIDDLE) +{ + // we are using a template version of this call to make sure + // the right StringWidth gets picked up for BView x BPoseView + // for max speed and flexibility + + // a standard ellipsis inserting fitting algorithm + if (view->StringWidth(str, length) <= width) + *result = str; + else { + const char *srcstr[1]; + char *results[1]; + + srcstr[0] = str; + results[0] = result->LockBuffer(length + 3); + + BFont font; + view->GetFont(&font); + + font.GetTruncatedStrings(srcstr, 1, truncMode, width, results); + result->UnlockBuffer(); + } + return view->StringWidth(result->String(), result->Length()); +} + + +WidgetAttributeText * +WidgetAttributeText::NewWidgetText(const Model *model, + const BColumn *column, const BPoseView *view) +{ + // call this to make the right WidgetAttributeText type for a + // given column + + const char *attrName = column->AttrName(); + + if (strcmp(attrName, kAttrPath) == 0) + return new PathAttributeText(model, column); + else if (strcmp(attrName, kAttrMIMEType) == 0) + return new KindAttributeText(model, column); + else if (strcmp(attrName, kAttrStatName) == 0) + return new NameAttributeText(model, column); + else if (strcmp(attrName, kAttrStatSize) == 0) + return new SizeAttributeText(model, column); + else if (strcmp(attrName, kAttrStatModified) == 0) + return new ModificationTimeAttributeText(model, column); + else if (strcmp(attrName, kAttrStatCreated) == 0) + return new CreationTimeAttributeText(model, column); +#ifdef OWNER_GROUP_ATTRIBUTES + else if (strcmp(attrName, kAttrStatOwner) == 0) + return new OwnerAttributeText(model, column); + else if (strcmp(attrName, kAttrStatGroup) == 0) + return new GroupAttributeText(model, column); +#endif + else if (strcmp(attrName, kAttrStatMode) == 0) + return new ModeAttributeText(model, column); + else if (strcmp(attrName, kAttrOpenWithRelation) == 0) + return new OpenWithRelationAttributeText(model, column, view); + else if (strcmp(attrName, kAttrAppVersion) == 0) + return new AppShortVersionAttributeText(model, column); + else if (strcmp(attrName, kAttrSystemVersion) == 0) + return new SystemShortVersionAttributeText(model, column); + else if (strcmp(attrName, kAttrOriginalPath) == 0) + return new OriginalPathAttributeText(model, column); + + return new GenericAttributeText(model, column); +} + + +WidgetAttributeText::WidgetAttributeText(const Model *model, const BColumn *column) + : + fModel(const_cast(model)), + fColumn(column), + fDirty(true), + fValueIsDefined(false) +{ + ASSERT(fColumn); + ASSERT(fColumn->Width() > 0); +} + + +WidgetAttributeText::~WidgetAttributeText() +{ +} + + +const char * +WidgetAttributeText::FittingText(const BPoseView *view) +{ + if (fDirty || fColumn->Width() != fOldWidth || !fValueIsDefined) + CheckViewChanged(view); + + ASSERT(!fDirty); + return fText.String(); +} + + +bool +WidgetAttributeText::CheckViewChanged(const BPoseView *view) +{ + BString newText; + FitValue(&newText, view); + + if (newText == fText) + return false; + + fText = newText; + return true; +} + + +float +WidgetAttributeText::TruncString(BString *result, const char *str, + int32 length, const BPoseView *view, float width, uint32 truncMode) +{ + return TruncStringBase(result, str, length, view, width, truncMode); +} + + +const char *kSizeFormats[] = { + "%.2f %s", + "%.1f %s", + "%.f %s", + "%.f%s", + 0 +}; + + +template +float +TruncFileSizeBase(BString *result, int64 value, const View *view, float width) +{ + // ToDo: + // if slow, replace float divisions with shifts + // if fast enough, try fitting more decimal places + + // format file size value + char buffer[1024]; + if (value == kUnknownSize) { + *result = "-"; + return view->StringWidth("-"); + } else if (value < kKBSize) { + sprintf(buffer, "%Ld bytes", value); + if (view->StringWidth(buffer) > width) + sprintf(buffer, "%Ld B", value); + } else { + const char *suffix; + float floatValue; + if (value >= kTBSize) { + suffix = "TB"; + floatValue = (float)value / kTBSize; + } else if (value >= kGBSize) { + suffix = "GB"; + floatValue = (float)value / kGBSize; + } else if (value >= kMBSize) { + suffix = "MB"; + floatValue = (float)value / kMBSize; + } else { + ASSERT(value >= kKBSize); + suffix = "KB"; + floatValue = (float)value / kKBSize; + } + + for (int32 index = 0; ; index++) { + if (!kSizeFormats[index]) + break; + + sprintf(buffer, kSizeFormats[index], floatValue, suffix); + + // strip off an insignificant zero so we don't get readings + // such as 1.00 + char *period = 0; + for (char *tmp = buffer; *tmp; tmp++) { + if (*tmp == '.') + period = tmp; + } + if (period && period[1] && period[2] == '0') + // move the rest of the string over the insignificant zero + for (char *tmp = &period[2]; *tmp; tmp++) + *tmp = tmp[1]; + + float resultWidth = view->StringWidth(buffer); + if (resultWidth <= width) { + *result = buffer; + return resultWidth; + } + } + } + + return TruncStringBase(result, buffer, (ssize_t)strlen(buffer), view, width, + (uint32)B_TRUNCATE_END); +} + + +float +WidgetAttributeText::TruncFileSize(BString *result, int64 value, + const BPoseView *view, float width) +{ + return TruncFileSizeBase(result, value, view, width); +} + +/* +const char *kTimeFormats[] = { + "%A, %B %d %Y, %I:%M:%S %p", // Monday, July 09 1997, 05:08:15 PM + "%a, %b %d %Y, %I:%M:%S %p", // Mon, Jul 09 1997, 05:08:15 PM + "%a, %b %d %Y, %I:%M %p", // Mon, Jul 09 1997, 05:08 PM + "%b %d %Y, %I:%M %p", // Jul 09 1997, 05:08 PM + "%m/%d/%y, %I:%M %p", // 07/09/97, 05:08 PM + "%m/%d/%y", // 07/09/97 + NULL +}; +*/ + +status_t +TimeFormat(BString &string, int32 index, FormatSeparator separator, + DateOrder order, bool clockIs24Hour) +{ + if (index >= 6) + return B_ERROR; + + BString clockString; + BString dateString; + + if (index <= 1) + if (clockIs24Hour) + clockString = "%H:%M:%S"; + else + clockString = "%I:%M:%S %p"; + else + if (clockIs24Hour) + clockString = "%H:%M"; + else + clockString = "%I:%M %p"; + + if (index <= 3) { + switch (order) { + case kYMDFormat: + dateString = "%Y %! %d"; + break; + case kDMYFormat: + dateString = "%d %! %Y"; + break; + case kMDYFormat: + // Fall through + case kDateFormatEnd: + // Fall through + default: + dateString = "%! %d %Y"; + break; + } + if (index == 0) + dateString.Replace('!', 'B', 1); + else + dateString.Replace('!', 'b', 1); + } else { + switch (order) { + case kYMDFormat: + dateString = "%y!%m!%d"; + break; + case kDMYFormat: + dateString = "%d!%m!%y"; + break; + case kMDYFormat: + // Fall through + case kDateFormatEnd: + // Fall through + default: + dateString = "%m!%d!%y"; + break; + } + + char separatorArray[] = {' ', '-', '/', '\\', '.'}; + + if (separator == kNoSeparator) + dateString.ReplaceAll("!", ""); + else + dateString.ReplaceAll('!', separatorArray[separator-1]); + } + + if (index == 0) + string = "%A, "; + else if (index < 3) + string = "%a, "; + else + string = ""; + + string << dateString; + + if (index < 5) + string << ", " << clockString; + + return B_OK; +} + + +template +float +TruncTimeBase(BString *result, int64 value, const View *view, float width) +{ + TrackerSettings settings; + FormatSeparator separator = settings.TimeFormatSeparator(); + DateOrder order = settings.DateOrderFormat(); + bool clockIs24hr = settings.ClockIs24Hr(); + + float resultWidth = 0; + char buffer[256]; + + time_t timeValue = (time_t)value; + tm timeData; + // use reentrant version of localtime to avoid having to have a semaphore + // (localtime uses a global structure to do it's conversion) + localtime_r(&timeValue, &timeData); + + BString timeFormat; + + for (int32 index = 0; ; index++) { + if (TimeFormat(timeFormat, index, separator, order, clockIs24hr) != B_OK) + break; + strftime(buffer, 256, timeFormat.String(), &timeData); + resultWidth = view->StringWidth(buffer); + if (resultWidth <= width) + break; + } + if (resultWidth > width) + // even the shortest format string didn't do it, insert ellipsis + resultWidth = TruncStringBase(result, buffer, (ssize_t)strlen(buffer), view, width); + else + *result = buffer; + + return resultWidth; +} + + +float +WidgetAttributeText::TruncTime(BString *result, int64 value, const BPoseView *view, + float width) +{ + return TruncTimeBase(result, value, view, width); +} + + +float +WidgetAttributeText::CurrentWidth() const +{ + return fTruncatedWidth; +} + + +float +WidgetAttributeText::Width(const BPoseView *pose) +{ + FittingText(pose); + return CurrentWidth(); +} + + +void +WidgetAttributeText::SetUpEditing(BTextView *) +{ + ASSERT(fColumn->Editable()); +} + + +bool +WidgetAttributeText::CommitEditedText(BTextView *) +{ + // can't do anything here at this point + TRESPASS(); + return false; +} + + +status_t +WidgetAttributeText::AttrAsString(const Model *model, BString *result, + const char *attrName, int32 attrType, + float width, BView *view, int64 *resultingValue) +{ + int64 value; + + status_t error = model->InitCheck(); + if (error != B_OK) + return error; + + switch (attrType) { + case B_TIME_TYPE: + if (strcmp(attrName, kAttrStatModified) == 0) + value = model->StatBuf()->st_mtime; + else if (strcmp(attrName, kAttrStatCreated) == 0) + value = model->StatBuf()->st_crtime; + else { + TRESPASS(); + // not yet supported + return B_ERROR; + } + TruncTimeBase(result, value, view, width); + if (resultingValue) + *resultingValue = value; + return B_OK; + + case B_STRING_TYPE: + if (strcmp(attrName, kAttrPath) == 0) { + BEntry entry(model->EntryRef()); + BPath path; + BString tmp; + + if (entry.InitCheck() == B_OK && entry.GetPath(&path) == B_OK) { + tmp = path.Path(); + TruncateLeaf(&tmp); + } else + tmp = "-"; + + if (width > 0) + TruncStringBase(result, tmp.String(), tmp.Length(), view, width); + else + *result = tmp.String(); + + return B_OK; + } + break; + + case kSizeType: +// TruncFileSizeBase(result, model->StatBuf()->st_size, view, width); + return B_OK; + break; + + default: + TRESPASS(); + // not yet supported + return B_ERROR; + + } + + TRESPASS(); + return B_ERROR; +} + + +bool +WidgetAttributeText::IsEditable() const +{ + return fColumn->Editable() && !BVolume(fModel->StatBuf()->st_dev).IsReadOnly(); +} + + +void +WidgetAttributeText::SetDirty(bool value) +{ + fDirty = value; +} + + +// #pragma mark - + + +StringAttributeText::StringAttributeText(const Model *model, const BColumn *column) + : WidgetAttributeText(model, column), + fValueDirty(true) +{ +} + + +const char * +StringAttributeText::Value() +{ + if (fValueDirty) + ReadValue(&fFullValueText); + + return fFullValueText.String(); +} + + +bool +StringAttributeText::CheckAttributeChanged() +{ + BString newString; + ReadValue(&newString); + + if (newString == fFullValueText) + return false; + + fFullValueText = newString; + fDirty = true; // have to redo fitted string + return true; +} + + +void +StringAttributeText::FitValue(BString *result, const BPoseView *view) +{ + if (fValueDirty) + ReadValue(&fFullValueText); + fOldWidth = fColumn->Width(); + + fTruncatedWidth = TruncString(result, fFullValueText.String(), fFullValueText.Length(), + view, fOldWidth); + fDirty = false; +} + + +float +StringAttributeText::PreferredWidth(const BPoseView *pose) const +{ + return pose->StringWidth(fFullValueText.String()); +} + + +int +StringAttributeText::Compare(WidgetAttributeText &attr, BPoseView *) +{ + StringAttributeText *compareTo = + dynamic_cast(&attr); + ASSERT(compareTo); + + if (fValueDirty) + ReadValue(&fFullValueText); + + return strcasecmp(fFullValueText.String(), compareTo->Value()); +} + + +bool +StringAttributeText::CommitEditedText(BTextView *textView) +{ + ASSERT(fColumn->Editable()); + const char *text = textView->Text(); + + if (fFullValueText == text) + // no change + return false; + + if (textView->TextLength() == 0) + // cannot do an empty name + return false; + + // cause re-truncation + fDirty = true; + + if (!CommitEditedTextFlavor(textView)) + return false; + + // update text and width in this widget + fFullValueText = text; + + return true; +} + + +// #pragma mark - + + +ScalarAttributeText::ScalarAttributeText(const Model *model, + const BColumn *column) + : WidgetAttributeText(model, column), + fValueDirty(true) +{ +} + + +int64 +ScalarAttributeText::Value() +{ + if (fValueDirty) + fValue = ReadValue(); + return fValue; +} + + +bool +ScalarAttributeText::CheckAttributeChanged() +{ + int64 newValue = ReadValue(); + if (newValue == fValue) + return false; + + fValue = newValue; + fDirty = true; // have to redo fitted string + return true; +} + + +float +ScalarAttributeText::PreferredWidth(const BPoseView *pose) const +{ + BString widthString; + widthString << fValue; + return pose->StringWidth(widthString.String()); +} + + +int +ScalarAttributeText::Compare(WidgetAttributeText &attr, BPoseView *) +{ + ScalarAttributeText *compareTo = + dynamic_cast(&attr); + ASSERT(compareTo); + // make sure we're not comparing apples and oranges + + if (fValueDirty) + fValue = ReadValue(); + + return fValue > compareTo->Value() ? (fValue == compareTo->Value() ? 0 : -1) : 1 ; +} + + +// #pragma mark - + + +PathAttributeText::PathAttributeText(const Model *model, const BColumn *column) + : StringAttributeText(model, column) +{ +} + + +void +PathAttributeText::ReadValue(BString *result) +{ + // get the path + BEntry entry(fModel->EntryRef()); + BPath path; + + if (entry.InitCheck() == B_OK && entry.GetPath(&path) == B_OK) { + *result = path.Path(); + TruncateLeaf(result); + } else + *result = "-"; + fValueDirty = false; +} + + +// #pragma mark - + + +OriginalPathAttributeText::OriginalPathAttributeText(const Model *model, const BColumn *column) + : StringAttributeText(model, column) +{ +} + + +void +OriginalPathAttributeText::ReadValue(BString *result) +{ + BEntry entry(fModel->EntryRef()); + BPath path; + + // get the original path + if (entry.InitCheck() == B_OK && FSGetOriginalPath(&entry, &path) == B_OK) + *result = path.Path(); + else + *result = "-"; + fValueDirty = false; +} + + +// #pragma mark - + + +KindAttributeText::KindAttributeText(const Model *model, const BColumn *column) + : StringAttributeText(model, column) +{ +} + + +void +KindAttributeText::ReadValue(BString *result) +{ + BMimeType mime; + char desc[B_MIME_TYPE_LENGTH]; + + // get the mime type + if (mime.SetType(fModel->MimeType()) != B_OK) + *result = "Unknown"; + // get the short mime type description + else if (mime.GetShortDescription(desc) == B_OK) + *result = desc; + else + *result = fModel->MimeType(); + fValueDirty = false; +} + + +// #pragma mark - + + +NameAttributeText::NameAttributeText(const Model *model, const BColumn *column) + : StringAttributeText(model, column) +{ +} + + +int +NameAttributeText::Compare(WidgetAttributeText &attr, BPoseView *) +{ + NameAttributeText *compareTo = dynamic_cast(&attr); + + ASSERT(compareTo); + + if (fValueDirty) + ReadValue(&fFullValueText); + + if (NameAttributeText::sSortFolderNamesFirst) + return fModel->CompareFolderNamesFirst(attr.TargetModel()); + + return strcasecmp(fFullValueText.String(), compareTo->Value()); +} + + +void +NameAttributeText::ReadValue(BString *result) +{ +#ifdef DEBUG +// x86 support :-) + if ((modifiers() & B_CAPS_LOCK) != 0) { + if (fModel->IsVolume()) { + BVolumeRoster roster; + roster.Rewind(); + BVolume volume; + char device = 'A'; + while (roster.GetNextVolume(&volume) == B_OK) { + char name[256]; + if (volume.GetName(name) == B_OK + && strcmp(name, fModel->Name()) == 0) { + *result += device; + *result += ':'; + fValueDirty = false; + return; + } + device++; + } + } + const char *modelName = fModel->Name(); + bool hasDot = strstr(".", modelName) != 0; + for (int32 index = 0; index < 8; index++) { + if (!modelName[index] || modelName[index] == '.') + break; + *result += toupper(modelName[index]); + } + if (hasDot) { + modelName = strstr(".", modelName); + for (int32 index = 0; index < 4; index++) { + if (!modelName[index]) + break; + *result += toupper(modelName[index]); + } + } else if (fModel->IsExecutable()) + *result += ".EXE"; + + } else +#endif + *result = fModel->Name(); + + fValueDirty = false; +} + + +void +NameAttributeText::FitValue(BString *result, const BPoseView *view) +{ + if (fValueDirty) + ReadValue(&fFullValueText); + fOldWidth = fColumn->Width(); + fTruncatedWidth = TruncString(result, fFullValueText.String(), fFullValueText.Length(), + view, fOldWidth, B_TRUNCATE_END); + fDirty = false; +} + + +void +NameAttributeText::SetUpEditing(BTextView *textView) +{ + DisallowFilenameKeys(textView); + + textView->SetMaxBytes(B_FILE_NAME_LENGTH); + textView->SetText(fFullValueText.String(), fFullValueText.Length()); +} + + +bool +NameAttributeText::CommitEditedTextFlavor(BTextView *textView) +{ + const char *text = textView->Text(); + + BEntry entry(fModel->EntryRef()); + if (entry.InitCheck() != B_OK) + return false; + + BDirectory parent; + if (entry.GetParent(&parent) != B_OK) + return false; + + bool removeExisting = false; + if (parent.Contains(text)) { + BAlert *alert = new BAlert("", "That name is already taken. " + "Please type another one.", "Replace other file", "OK", NULL, + B_WIDTH_AS_USUAL, B_WARNING_ALERT); + + alert->SetShortcut(0, 'r'); + + if (alert->Go()) + return false; + + removeExisting = true; + } + + // ToDo: + // use model-flavor specific virtuals for all of these special + // renamings + status_t result; + if (fModel->IsVolume()) { + BVolume volume(fModel->NodeRef()->device); + result = volume.InitCheck(); + if (result == B_OK) { + RenameVolumeUndo undo(volume, text); + + result = volume.SetName(text); + if (result != B_OK) + undo.Remove(); + } + } else { + if (fModel->IsQuery()) { + BModelWriteOpener opener(fModel); + ASSERT(fModel->Node()); + MoreOptionsStruct::SetQueryTemporary(fModel->Node(), false); + } + + RenameUndo undo(entry, text); + + result = entry.Rename(text, removeExisting); + if (result != B_OK) + undo.Remove(); + } + + return result == B_OK; +} + +bool NameAttributeText::sSortFolderNamesFirst = false; + +void +NameAttributeText::SetSortFolderNamesFirst(bool enabled) +{ + NameAttributeText::sSortFolderNamesFirst = enabled; +} + + +// #pragma mark - + +#ifdef OWNER_GROUP_ATTRIBUTES + +OwnerAttributeText::OwnerAttributeText(const Model *model, const BColumn *column) + : StringAttributeText(model, column) +{ +} + + +void +OwnerAttributeText::ReadValue(BString *result) +{ + uid_t nodeOwner = fModel->StatBuf()->st_uid; + BString user; + + if (nodeOwner == 0) { + if (getenv("USER") != NULL) + user << getenv("USER"); + else + user << "root"; + } else + user << nodeOwner; + *result = user.String(); + + fValueDirty = false; +} + + +GroupAttributeText::GroupAttributeText(const Model *model, const BColumn *column) + : StringAttributeText(model, column) +{ +} + + +void +GroupAttributeText::ReadValue(BString *result) +{ + gid_t nodeGroup = fModel->StatBuf()->st_gid; + BString group; + + if (nodeGroup == 0) { + if (getenv("GROUP") != NULL) + group << getenv("GROUP"); + else + group << "0"; + } else + group << nodeGroup; + *result = group.String(); + + fValueDirty = false; +} + +#endif /* OWNER_GROUP_ATTRIBUTES */ + +ModeAttributeText::ModeAttributeText(const Model *model, const BColumn *column) + : StringAttributeText(model, column) +{ +} + + +void +ModeAttributeText::ReadValue(BString *result) +{ + mode_t mode = fModel->StatBuf()->st_mode; + mode_t baseMask = 00400; + char buffer[11]; + + char *scanner = buffer; + + if (S_ISDIR(mode)) + *scanner++ = 'd'; + else if (S_ISLNK(mode)) + *scanner++ = 'l'; + else if (S_ISBLK(mode)) + *scanner++ = 'b'; + else if (S_ISCHR(mode)) + *scanner++ = 'c'; + else + *scanner++ = '-'; + + for (int32 index = 0; index < 9; index++) { + *scanner++ = (mode & baseMask) ? "rwx"[index % 3] : '-'; + baseMask >>= 1; + } + + *scanner = 0; + *result = buffer; + + fValueDirty = false; +} + + +// #pragma mark - + + +SizeAttributeText::SizeAttributeText(const Model *model, const BColumn *column) + : ScalarAttributeText(model, column) +{ +} + + +int64 +SizeAttributeText::ReadValue() +{ + fValueDirty = false; + // get the size + + if (fModel->IsVolume()) { + BVolume volume(fModel->NodeRef()->device); + + return volume.Capacity(); + } + + if (fModel->IsDirectory() || fModel->IsQuery() + || fModel->IsQueryTemplate() || fModel->IsSymLink()) + return kUnknownSize; + + fValueIsDefined = true; + + return fModel->StatBuf()->st_size; +} + + +void +SizeAttributeText::FitValue(BString *result, const BPoseView *view) +{ + if (fValueDirty) + fValue = ReadValue(); + fOldWidth = fColumn->Width(); + fTruncatedWidth = TruncFileSize(result, fValue, view, fOldWidth); + fDirty = false; +} + + +float +SizeAttributeText::PreferredWidth(const BPoseView *pose) const +{ + if (fValueIsDefined) { + BString widthString; + TruncFileSize(&widthString, fValue, pose, 100000); + return pose->StringWidth(widthString.String()); + } + return pose->StringWidth("-"); +} + + +// #pragma mark - + + +TimeAttributeText::TimeAttributeText(const Model *model, const BColumn *column) + : ScalarAttributeText(model, column) +{ +} + + +float +TimeAttributeText::PreferredWidth(const BPoseView *pose) const +{ + BString widthString; + TruncTimeBase(&widthString, fValue, pose, 100000); + return pose->StringWidth(widthString.String()); +} + + +void +TimeAttributeText::FitValue(BString *result, const BPoseView *view) +{ + if (fValueDirty) + fValue = ReadValue(); + fOldWidth = fColumn->Width(); + fTruncatedWidth = TruncTime(result, fValue, view, fOldWidth); + fDirty = false; +} + + +CreationTimeAttributeText::CreationTimeAttributeText(const Model *model, + const BColumn *column) + : TimeAttributeText(model, column) +{ +} + + +int64 +CreationTimeAttributeText::ReadValue() +{ + fValueDirty = false; + fValueIsDefined = true; + return fModel->StatBuf()->st_crtime; +} + +ModificationTimeAttributeText::ModificationTimeAttributeText(const Model *model, + const BColumn *column) + : TimeAttributeText(model, column) +{ +} + + +int64 +ModificationTimeAttributeText::ReadValue() +{ + fValueDirty = false; + fValueIsDefined = true; + return fModel->StatBuf()->st_mtime; +} + + +// #pragma mark - + +const int32 kGenericReadBufferSize = 1024; + +GenericAttributeText::GenericAttributeText(const Model *model, const BColumn *column) + : WidgetAttributeText(model, column), + fValueDirty(true) +{ +} + + +bool +GenericAttributeText::CheckAttributeChanged() +{ + GenericValueStruct tmpValue = fValue; + BString tmpString(fFullValueText); + ReadValue(); + + // fDirty could already be true, in that case we mustn't set it to + // false, even if the attribute text hasn't changed + bool changed = (fValue.int64t != tmpValue.int64t) || (tmpString != fFullValueText); + if (changed) + fDirty = true; + + return fDirty; +} + + +float +GenericAttributeText::PreferredWidth(const BPoseView *pose) const +{ + return pose->StringWidth(fFullValueText.String()); +} + + +void +GenericAttributeText::ReadValue() +{ + BModelOpener opener(const_cast(fModel)); + + ssize_t length = 0; + fFullValueText = "-"; + fValue.int64t = 0; + fValueIsDefined = false; + fValueDirty = false; + + if (!fModel->Node()) + return; + + switch (fColumn->AttrType()) { + case B_STRING_TYPE: + { + char buffer[kGenericReadBufferSize]; + length = fModel->Node()->ReadAttr(fColumn->AttrName(), + fColumn->AttrType(), 0, buffer, kGenericReadBufferSize - 1); + + if (length > 0) { + buffer[length] = '\0'; + // make sure the buffer is null-terminated even if we + // didn't read the whole attribute in or it wasn't to + // begin with + + fFullValueText = buffer; + fValueIsDefined = true; + } + break; + } + + case B_SSIZE_T_TYPE: + case B_TIME_TYPE: + case B_OFF_T_TYPE: + case B_FLOAT_TYPE: + case B_BOOL_TYPE: + case B_CHAR_TYPE: + case B_INT8_TYPE: + case B_INT16_TYPE: + case B_INT32_TYPE: + case B_INT64_TYPE: + case B_UINT8_TYPE: + case B_UINT16_TYPE: + case B_UINT32_TYPE: + case B_UINT64_TYPE: + case B_DOUBLE_TYPE: + { + // read in the numerical bit representation and attach it + // with a type, depending on the bytes that could be read + attr_info info; + GenericValueStruct tmp; + if (fModel->Node()->GetAttrInfo(fColumn->AttrName(), &info) == B_OK) { + if (info.size && info.size <= sizeof(int64)) { + length = fModel->Node()->ReadAttr(fColumn->AttrName(), + fColumn->AttrType(), 0, &tmp, (size_t)info.size); + } + + // We used tmp as a block of memory, now set the correct fValue: + + if (length == info.size) { + if (fColumn->AttrType() == B_FLOAT_TYPE + || fColumn->AttrType() == B_DOUBLE_TYPE) { + // filter out special float/double types + switch (info.size) { + case sizeof(float): + fValueIsDefined = true; + fValue.floatt = tmp.floatt; + break; + + case sizeof(double): + fValueIsDefined = true; + fValue.doublet = tmp.doublet; + break; + + default: + TRESPASS(); + } + } else { + // handle the standard data types + switch (info.size) { + case sizeof(char): // Takes care of bool, too. + fValueIsDefined = true; + fValue.int8t = tmp.int8t; + break; + + case sizeof(int16): + fValueIsDefined = true; + fValue.int16t = tmp.int16t; + break; + + case sizeof(int32): // Takes care of time_t, too. + fValueIsDefined = true; + fValue.int32t = tmp.int32t; + break; + + case sizeof(int64): // Taked care of off_t, too. + fValueIsDefined = true; + fValue.int64t = tmp.int64t; + break; + + default: + TRESPASS(); + } + } + } + } + break; + } + } +} + + +void +GenericAttributeText::FitValue(BString *result, const BPoseView *view) +{ + if (fValueDirty) + ReadValue(); + + fOldWidth = fColumn->Width(); + + if (!fValueIsDefined) { + *result = "-"; + fTruncatedWidth = TruncString(result, fFullValueText.String(), + fFullValueText.Length(), view, fOldWidth); + fDirty = false; + return; + } + + char buffer[256]; + + switch (fColumn->AttrType()) { + case B_SIZE_T_TYPE: + TruncFileSizeBase(result, fValue.int32t, view, fOldWidth); + return; + + case B_SSIZE_T_TYPE: + if (fValue.int32t > 0) { + TruncFileSizeBase(result, fValue.int32t, view, fOldWidth); + return; + } + sprintf(buffer, "%s", strerror(fValue.int32t)); + fFullValueText = buffer; + break; + + case B_STRING_TYPE: + fTruncatedWidth = TruncString(result, fFullValueText.String(), + fFullValueText.Length(), view, fOldWidth); + fDirty = false; + return; + + case B_OFF_T_TYPE: + // as a side effect update the fFullValueText to the string representation + // of value + TruncFileSize(&fFullValueText, fValue.off_tt, view, 100000); + fTruncatedWidth = TruncFileSize(result, fValue.off_tt, view, fOldWidth); + fDirty = false; + return; + + case B_TIME_TYPE: + // as a side effect update the fFullValueText to the string representation + // of value + TruncTime(&fFullValueText, fValue.time_tt, view, 100000); + fTruncatedWidth = TruncTime(result, fValue.time_tt, view, fOldWidth); + fDirty = false; + return; + + case B_BOOL_TYPE: + // For now use true/false, would be nice to be able to set + // the value text + + sprintf(buffer, "%s", fValue.boolt ? "true" : "false"); + fFullValueText = buffer; + break; + + case B_CHAR_TYPE: + // Make sure no non-printable characters are displayed: + if (!isprint(fValue.uint8t)) { + *result = "-"; + fTruncatedWidth = TruncString(result, fFullValueText.String(), + fFullValueText.Length(), view, fOldWidth); + fDirty = false; + return; + } + + sprintf(buffer, "%c", fValue.uint8t); + fFullValueText = buffer; + break; + + case B_INT8_TYPE: + sprintf(buffer, "%d", fValue.int8t); + fFullValueText = buffer; + break; + + case B_UINT8_TYPE: + sprintf(buffer, "%d", fValue.uint8t); + fFullValueText = buffer; + break; + + case B_INT16_TYPE: + sprintf(buffer, "%d", fValue.int16t); + fFullValueText = buffer; + break; + + case B_UINT16_TYPE: + sprintf(buffer, "%d", fValue.uint16t); + fFullValueText = buffer; + break; + + case B_INT32_TYPE: + sprintf(buffer, "%ld", fValue.int32t); + fFullValueText = buffer; + break; + + case B_UINT32_TYPE: + sprintf(buffer, "%ld", fValue.uint32t); + fFullValueText = buffer; + break; + + case B_INT64_TYPE: + sprintf(buffer, "%Ld", fValue.int64t); + fFullValueText = buffer; + break; + + case B_UINT64_TYPE: + sprintf(buffer, "%Ld", fValue.uint64t); + fFullValueText = buffer; + break; + + case B_FLOAT_TYPE: + if (fabs(fValue.floatt) >= 10000 + || fabs(fValue.floatt) < 0.01) + // The %f conversion can possibly overflow 'buffer' if the value is + // too big, since it doesn't print in exponent form. Ever. + sprintf(buffer, "%.3e", fValue.floatt); + else + sprintf(buffer, "%.3f", fValue.floatt); + fFullValueText = buffer; + break; + + case B_DOUBLE_TYPE: + if (fabs(fValue.doublet) >= 10000 + || fabs(fValue.doublet) < 0.01) + // The %f conversion can possibly overflow 'buffer' if the value is + // too big, since it doesn't print in exponent form. Ever. + sprintf(buffer, "%.3e", fValue.doublet); + else + sprintf(buffer, "%.3f", fValue.doublet); + fFullValueText = buffer; + break; + + default: + *result = "-"; + fTruncatedWidth = TruncString(result, fFullValueText.String(), + fFullValueText.Length(), view, fOldWidth); + fDirty = false; + return; + } + fTruncatedWidth = TruncString(result, buffer, (ssize_t)strlen(buffer), view, fOldWidth); + fDirty = false; +} + + +int +GenericAttributeText::Compare(WidgetAttributeText &attr, BPoseView *) +{ + GenericAttributeText *compareTo = + dynamic_cast(&attr); + ASSERT(compareTo); + + if (fValueDirty) + ReadValue(); + if (compareTo->fValueDirty) + compareTo->ReadValue(); + + // Sort undefined values last, regardless of the other value: + if (fValueIsDefined == false || compareTo->fValueIsDefined == false) + return fValueIsDefined < compareTo->fValueIsDefined ? + (fValueIsDefined == compareTo->fValueIsDefined ? 0 : -1) : 1; + + switch (fColumn->AttrType()) { + case B_STRING_TYPE: + return fFullValueText.ICompare(compareTo->fFullValueText); + + case B_CHAR_TYPE: + { + char vStr[2] = { static_cast(fValue.uint8t), 0 }; + char cStr[2] = { static_cast(compareTo->fValue.uint8t), 0}; + + BString valueStr(vStr); + BString compareToStr(cStr); + + return valueStr.ICompare(compareToStr); + } + + case B_FLOAT_TYPE: + return fValue.floatt > compareTo->fValue.floatt ? + (fValue.floatt == compareTo->fValue.floatt ? 0 : -1) : 1; + + case B_DOUBLE_TYPE: + return fValue.doublet > compareTo->fValue.doublet ? + (fValue.doublet == compareTo->fValue.doublet ? 0 : -1) : 1; + + case B_BOOL_TYPE: + return fValue.boolt > compareTo->fValue.boolt ? + (fValue.boolt == compareTo->fValue.boolt ? 0 : -1) : 1; + + case B_UINT8_TYPE: + return fValue.uint8t > compareTo->fValue.uint8t ? + (fValue.uint8t == compareTo->fValue.uint8t ? 0 : -1) : 1; + + case B_INT8_TYPE: + return fValue.int8t > compareTo->fValue.int8t ? + (fValue.int8t == compareTo->fValue.int8t ? 0 : -1) : 1; + + case B_UINT16_TYPE: + return fValue.uint16t > compareTo->fValue.uint16t ? + (fValue.uint16t == compareTo->fValue.uint16t ? 0 : -1) : 1; + + case B_INT16_TYPE: + return fValue.int16t > compareTo->fValue.int16t ? + (fValue.int16t == compareTo->fValue.int16t ? 0 : -1) : 1; + + case B_UINT32_TYPE: + return fValue.uint32t > compareTo->fValue.uint32t ? + (fValue.uint32t == compareTo->fValue.uint32t ? 0 : -1) : 1; + + case B_TIME_TYPE: + // time_t typedef'd to a long, i.e. a int32 + case B_INT32_TYPE: + return fValue.int32t > compareTo->fValue.int32t ? + (fValue.int32t == compareTo->fValue.int32t ? 0 : -1) : 1; + + case B_OFF_T_TYPE: + // off_t typedef'd to a long long, i.e. a int64 + case B_INT64_TYPE: + return fValue.int64t > compareTo->fValue.int64t ? + (fValue.int64t == compareTo->fValue.int64t ? 0 : -1) : 1; + + case B_UINT64_TYPE: + default: + return fValue.uint64t > compareTo->fValue.uint64t ? + (fValue.uint64t == compareTo->fValue.uint64t ? 0 : -1) : 1; + } + return 0; +} + + +bool +GenericAttributeText::CommitEditedText(BTextView *textView) +{ + ASSERT(fColumn->Editable()); + const char *text = textView->Text(); + + if (fFullValueText == text) + // no change + return false; + + if (!CommitEditedTextFlavor(textView)) + return false; + + // update text and width in this widget + fFullValueText = text; + // cause re-truncation + fDirty = true; + fValueDirty = true; + + return true; +} + + +void +GenericAttributeText::SetUpEditing(BTextView *textView) +{ + textView->SetMaxBytes(kGenericReadBufferSize - 1); + textView->SetText(fFullValueText.String(), fFullValueText.Length()); +} + + +bool +GenericAttributeText::CommitEditedTextFlavor(BTextView *textView) +{ + BNode node(fModel->EntryRef()); + + if (node.InitCheck() != B_OK) + return false; + + uint32 type = fColumn->AttrType(); + + if (type != B_STRING_TYPE + && type != B_UINT64_TYPE + && type != B_UINT32_TYPE + && type != B_UINT16_TYPE + && type != B_UINT8_TYPE + && type != B_INT64_TYPE + && type != B_INT32_TYPE + && type != B_INT16_TYPE + && type != B_INT8_TYPE + && type != B_OFF_T_TYPE + && type != B_TIME_TYPE + && type != B_FLOAT_TYPE + && type != B_DOUBLE_TYPE + && type != B_CHAR_TYPE + && type != B_BOOL_TYPE) { + (new BAlert("", "Sorry, you cannot edit that attribute.", + "Cancel", 0, 0, B_WIDTH_AS_USUAL, B_STOP_ALERT))->Go(); + return false; + } + + const char *columnName = fColumn->AttrName(); + ssize_t size = 0; + + switch (type) { + case B_STRING_TYPE: + size = fModel->WriteAttr(columnName, + type, 0, textView->Text(), (size_t)(textView->TextLength() + 1)); + break; + + case B_BOOL_TYPE: + { + bool value = strncasecmp(textView->Text(), "0", 1) != 0 + && strncasecmp(textView->Text(), "off", 2) != 0 + && strncasecmp(textView->Text(), "no", 3) != 0 + && strncasecmp(textView->Text(), "false", 4) != 0 + && strlen(textView->Text()) != 0; + + size = fModel->WriteAttr(columnName, type, 0, &value, sizeof(bool)); + break; + } + + case B_CHAR_TYPE: + { + char ch; + sscanf(textView->Text(), "%c", &ch); + //Check if we read the start of a multi-byte glyph: + if (!isprint(ch)) { + (new BAlert("", "Sorry, The 'Character' attribute cannot store a multi-byte glyph.", + "Cancel", 0, 0, B_WIDTH_AS_USUAL, B_STOP_ALERT))->Go(); + return false; + } + + size = fModel->WriteAttr(columnName, type, 0, &ch, sizeof(char)); + break; + } + + case B_FLOAT_TYPE: + { + float floatVal; + + if (sscanf(textView->Text(), "%f", &floatVal) == 1) { + fValueIsDefined = true; + fValue.floatt = floatVal; + size = fModel->WriteAttr(columnName, type, 0, &floatVal, sizeof(float)); + } else + // If the value was already defined, it's on disk. Otherwise not. + return fValueIsDefined; + break; + } + + case B_DOUBLE_TYPE: + { + double doubleVal; + + if (sscanf(textView->Text(), "%lf", &doubleVal) == 1) { + fValueIsDefined = true; + fValue.doublet = doubleVal; + size = fModel->WriteAttr(columnName, type, 0, &doubleVal, sizeof(double)); + } else + // If the value was already defined, it's on disk. Otherwise not. + return fValueIsDefined; + break; + } + + case B_TIME_TYPE: + case B_OFF_T_TYPE: + case B_UINT64_TYPE: + case B_UINT32_TYPE: + case B_UINT16_TYPE: + case B_UINT8_TYPE: + case B_INT64_TYPE: + case B_INT32_TYPE: + case B_INT16_TYPE: + case B_INT8_TYPE: + { + GenericValueStruct tmp; + size_t scalarSize = 0; + + switch (type) { + case B_TIME_TYPE: + tmp.time_tt = parsedate(textView->Text(), time(0)); + scalarSize = sizeof(time_t); + break; + + // do some size independent conversion on builtin types + case B_OFF_T_TYPE: + tmp.off_tt = StringToScalar(textView->Text()); + scalarSize = sizeof(off_t); + break; + + case B_UINT64_TYPE: + case B_INT64_TYPE: + tmp.int64t = StringToScalar(textView->Text()); + scalarSize = sizeof(int64); + break; + + case B_UINT32_TYPE: + case B_INT32_TYPE: + tmp.int32t = (int32)StringToScalar(textView->Text()); + scalarSize = sizeof(int32); + break; + + case B_UINT16_TYPE: + case B_INT16_TYPE: + tmp.int16t = (int16)StringToScalar(textView->Text()); + scalarSize = sizeof(int16); + break; + + case B_UINT8_TYPE: + case B_INT8_TYPE: + tmp.int8t = (int8)StringToScalar(textView->Text()); + scalarSize = sizeof(int8); + break; + + default: + TRESPASS(); + + } + + size = fModel->WriteAttr(columnName, type, 0, &tmp, scalarSize); + break; + } + } + + if (size < 0) { + (new BAlert("", "There was an error writing the attribute.", + "Cancel", 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); + + fValueIsDefined = false; + return false; + } + + fValueIsDefined = true; + return true; +} + + +// #pragma mark - + + +OpenWithRelationAttributeText::OpenWithRelationAttributeText(const Model *model, + const BColumn *column, const BPoseView *view) + : ScalarAttributeText(model, column), + fPoseView(view) +{ +} + + +int64 +OpenWithRelationAttributeText::ReadValue() +{ + fValueDirty = false; + + const OpenWithPoseView *view = dynamic_cast(fPoseView); + + if (view) { + fValue = view->OpenWithRelation(fModel); + fValueIsDefined = true; + } + + return fValue; +} + + +void +OpenWithRelationAttributeText::FitValue(BString *result, const BPoseView *view) +{ + if (fValueDirty) + ReadValue(); + + ASSERT(view == fPoseView); + const OpenWithPoseView *launchWithView = + dynamic_cast(view); + + if (launchWithView) + launchWithView->OpenWithRelationDescription(fModel, &fRelationText); + + fOldWidth = fColumn->Width(); + fTruncatedWidth = TruncString(result, fRelationText.String(), fRelationText.Length(), + view, fOldWidth, B_TRUNCATE_END); + fDirty = false; +} + + +VersionAttributeText::VersionAttributeText(const Model *model, + const BColumn *column, bool app) + : StringAttributeText(model, column), + fAppVersion(app) +{ +} + + +void +VersionAttributeText::ReadValue(BString *result) +{ + fValueDirty = false; + + BModelOpener opener(fModel); + BFile *file = dynamic_cast(fModel->Node()); + if (file) { + BAppFileInfo info(file); + version_info version; + if (info.InitCheck() == B_OK + && info.GetVersionInfo(&version, + fAppVersion ? B_APP_VERSION_KIND : B_SYSTEM_VERSION_KIND) == B_OK) { + *result = version.short_info; + return; + } + } + *result = "-"; +} + diff --git a/src/kits/tracker/WidgetAttributeText.h b/src/kits/tracker/WidgetAttributeText.h new file mode 100644 index 0000000000..eeac15c583 --- /dev/null +++ b/src/kits/tracker/WidgetAttributeText.h @@ -0,0 +1,389 @@ +/* +Open Tracker License + +Terms and Conditions + +Copyright (c) 1991-2000, Be Incorporated. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice applies to all licensees +and shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of Be Incorporated shall not be +used in advertising or otherwise to promote the sale, use or other dealings in +this Software without prior written authorization from Be Incorporated. + +Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks +of Be Incorporated in the United States and other countries. Other brand product +names are registered trademarks or trademarks of their respective holders. +All rights reserved. +*/ + +#ifndef __TEXT_WIDGET_ATTRIBUTE__ +#define __TEXT_WIDGET_ATTRIBUTE__ + +#include + +#include "TrackerSettings.h" + +namespace BPrivate { + +class Model; +class BPoseView; +class BColumn; + +// Tracker-only type for truncating the size string +// (Used in InfoWindow.cpp) +const uint32 kSizeType = 'kszt'; + +class WidgetAttributeText { + // each of subclasses knows how to retrieve a specific attribute + // from a model that is passed in and knows how to display the + // corresponding text, fitted into a specified width using a given + // view + // It is being asked for the string value by the TextWidget object + public: + WidgetAttributeText(const Model *, const BColumn *); + virtual ~WidgetAttributeText(); + + virtual bool CheckAttributeChanged() = 0; + // returns true if attribute value changed + + bool CheckViewChanged(const BPoseView *); + // returns true if fitted text changed, either because value + // changed or because width/view changed + + const char *FittingText(const BPoseView *); + // returns text, recalculating if not yet calculated + + virtual int Compare(WidgetAttributeText &, BPoseView *view) = 0; + // override to define a compare of two different attributes for + // sorting + + static WidgetAttributeText *NewWidgetText(const Model *, const BColumn *, + const BPoseView *); + // WidgetAttributeText factory + // call this to make the right WidgetAttributeText type for a + // given column + + float Width(const BPoseView *); + // respects the width of the corresponding column + float CurrentWidth() const; + // return the item width we got during our last fitting attempt + + virtual void SetUpEditing(BTextView *); + // set up the passed textView for the specifics of a given + // attribute editing + virtual bool CommitEditedText(BTextView *) = 0; + // return true if attribute actually changed + + virtual float PreferredWidth(const BPoseView *) const = 0; + + static status_t AttrAsString(const Model *model, BString *result, + const char *attrName, int32 attrType, float width, + BView *view, int64 *value = 0); + + Model *TargetModel() const; + + bool IsEditable() const; + + void SetDirty(bool); + + protected: + // generic fitting routines used by the different attributes + static float TruncString(BString *result, const char *src, + int32 length, const BPoseView *, float width, + uint32 truncMode = B_TRUNCATE_MIDDLE); + + static float TruncTime(BString *result, int64 src, + const BPoseView *view, float width); + + static float TruncFileSize(BString *result, int64 src, + const BPoseView *view, float width); + + virtual void FitValue(BString *result, const BPoseView *) = 0; + // override FitValue to do a specific text fitting for a given + // attribute + + mutable Model *fModel; + const BColumn *fColumn; + float fOldWidth; // ToDo: make these int32 only + float fTruncatedWidth; + bool fDirty; + // if true, need to recalculate text next time we try to use it + bool fValueIsDefined; + BString fText; + // holds the truncated text, fit to the parameters passed in + // in the last FittingText call +}; + +inline Model * +WidgetAttributeText::TargetModel() const +{ + return fModel; +} + + +class StringAttributeText : public WidgetAttributeText { + public: + StringAttributeText(const Model *, const BColumn *); + const char *Value(); + // returns the untrucated text that corresponds to the attribute + // value + virtual bool CheckAttributeChanged(); + + virtual float PreferredWidth(const BPoseView *) const; + + virtual bool CommitEditedText(BTextView *); + + protected: + virtual bool CommitEditedTextFlavor(BTextView *) { return false; } + + virtual void FitValue(BString *result, const BPoseView *); + virtual void ReadValue(BString *result) = 0; + + virtual int Compare(WidgetAttributeText &, BPoseView *view); + BString fFullValueText; + bool fValueDirty; + // used for lazy read, managed by ReadValue +}; + + +class ScalarAttributeText : public WidgetAttributeText { + public: + ScalarAttributeText(const Model *, const BColumn *); + int64 Value(); + virtual bool CheckAttributeChanged(); + + virtual float PreferredWidth(const BPoseView *) const; + + virtual bool CommitEditedText(BTextView *) { return false; } + // return true if attribute actually changed + protected: + virtual int64 ReadValue() = 0; + virtual int Compare(WidgetAttributeText &, BPoseView *view); + int64 fValue; + bool fValueDirty; + // used for lazy read, managed by ReadValue +}; + + +union GenericValueStruct { + time_t time_tt; + off_t off_tt; + + bool boolt; + int8 int8t; + uint8 uint8t; + int16 int16t; + int16 uint16t; + int32 int32t; + int32 uint32t; + int64 int64t; + int64 uint64t; + + float floatt; + double doublet; +}; + + +class GenericAttributeText : public WidgetAttributeText { + // used for displaying mime extra attributes + // supports different formats + public: + GenericAttributeText(const Model *, const BColumn *); + virtual bool CheckAttributeChanged(); + + virtual float PreferredWidth(const BPoseView *) const; + + virtual int Compare(WidgetAttributeText &, BPoseView *view); + + virtual void SetUpEditing(BTextView *); + virtual bool CommitEditedText(BTextView *); + + private: + virtual bool CommitEditedTextFlavor(BTextView *); + + virtual void FitValue(BString *result, const BPoseView *); + virtual void ReadValue(); + + // ToDo: + // split this up into a scalar flavor and string flavor + // to save memory + BString fFullValueText; + GenericValueStruct fValue; + bool fValueDirty; +}; + + +class TimeAttributeText : public ScalarAttributeText { + protected: + TimeAttributeText(const Model *, const BColumn *); + virtual float PreferredWidth(const BPoseView *) const; + virtual void FitValue(BString *result, const BPoseView *); +}; + + +class PathAttributeText : public StringAttributeText { + public: + PathAttributeText(const Model *, const BColumn *); + protected: + virtual void ReadValue(BString *result); +}; + + +class OriginalPathAttributeText : public StringAttributeText { + public: + OriginalPathAttributeText(const Model *, const BColumn *); + protected: + virtual void ReadValue(BString *result); +}; + + +class KindAttributeText : public StringAttributeText { + public: + KindAttributeText(const Model *, const BColumn *); + protected: + virtual void ReadValue(BString *result); +}; + + +class NameAttributeText : public StringAttributeText { + public: + NameAttributeText(const Model *, const BColumn *); + virtual void SetUpEditing(BTextView *); + virtual void FitValue(BString *result, const BPoseView *); + + static void SetSortFolderNamesFirst(bool); + protected: + virtual bool CommitEditedTextFlavor(BTextView *); + virtual int Compare(WidgetAttributeText &, BPoseView *view); + virtual void ReadValue(BString *result); + + static bool sSortFolderNamesFirst; +}; + + +#ifdef OWNER_GROUP_ATTRIBUTES + +class OwnerAttributeText : public StringAttributeText { + public: + OwnerAttributeText(const Model *, const BColumn *); + + protected: + virtual void ReadValue(BString *result); +}; + + +class GroupAttributeText : public StringAttributeText { + public: + GroupAttributeText(const Model *, const BColumn *); + + protected: + virtual void ReadValue(BString *result); +}; + +#endif /* OWNER_GROUP_ATTRIBUTES */ + +class ModeAttributeText : public StringAttributeText { + public: + ModeAttributeText(const Model *, const BColumn *); + + protected: + virtual void ReadValue(BString *result); +}; + +const int64 kUnknownSize = -1; + +class SizeAttributeText : public ScalarAttributeText { + public: + SizeAttributeText(const Model *, const BColumn *); + + protected: + virtual void FitValue(BString *result, const BPoseView *); + virtual int64 ReadValue(); + virtual float PreferredWidth(const BPoseView *) const; +}; + + +class CreationTimeAttributeText : public TimeAttributeText { + public: + CreationTimeAttributeText(const Model *, const BColumn *); + protected: + virtual int64 ReadValue(); +}; + + +class ModificationTimeAttributeText : public TimeAttributeText { + public: + ModificationTimeAttributeText(const Model *, const BColumn *); + + protected: + virtual int64 ReadValue(); +}; + + +class OpenWithRelationAttributeText : public ScalarAttributeText { + public: + OpenWithRelationAttributeText(const Model *, const BColumn *, + const BPoseView *); + + protected: + virtual void FitValue(BString *result, const BPoseView *); + virtual int64 ReadValue(); + + const BPoseView *fPoseView; + BString fRelationText; +}; + + +class VersionAttributeText : public StringAttributeText { + public: + VersionAttributeText(const Model *, const BColumn *, bool appVersion); + + protected: + virtual void ReadValue(BString *result); + private: + bool fAppVersion; +}; + + +class AppShortVersionAttributeText : public VersionAttributeText { + public: + AppShortVersionAttributeText(const Model *model, const BColumn *column) + : VersionAttributeText(model, column, true) + { + } +}; + + +class SystemShortVersionAttributeText : public VersionAttributeText { + public: + SystemShortVersionAttributeText(const Model *model, const BColumn *column) + : VersionAttributeText(model, column, false) + { + } +}; + +} // namespace BPrivate + + +extern status_t TimeFormat(BString &string, int32 index, FormatSeparator format, + DateOrder order, bool clockIs24Hour); + +using namespace BPrivate; + +#endif /* __TEXT_WIDGET_ATTRIBUTE__ */