* The type ahead mechanism did not work correctly in list view mode: instead
of taking the contents of the columns, it would always use the file name instead. * That uncovered another bug, though: WidgetText::Text() tried to cast everything to StringAttributeText, but GenericAttributeText is actually used for most columns. * Therefore, GenericAttributeText is now a subclass from StringAttributeText. * Extended the type ahead to take other columns into account, and made it also find strings in the middle (like a filter) as a second choice. * Whitespace cleanup. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@28180 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
@@ -6017,33 +6017,50 @@ BPoseView::FindNextMatch(int32 *matchingIndex, bool reverse)
|
||||
BPose *
|
||||
BPoseView::FindBestMatch(int32 *index)
|
||||
{
|
||||
char bestSoFar[B_FILE_NAME_LENGTH] = { 0 };
|
||||
BPose *poseToSelect = NULL;
|
||||
|
||||
BColumn *firstColumn = FirstColumn();
|
||||
float bestScore = -1;
|
||||
int32 count = fPoseList->CountItems();
|
||||
size_t matchLength = strlen(sMatchString);
|
||||
|
||||
// loop through all poses to find match
|
||||
int32 count = fPoseList->CountItems();
|
||||
for (int32 j = 0; j < CountColumns(); j++) {
|
||||
BColumn *column = ColumnAt(j);
|
||||
|
||||
for (int32 i = 0; i < count; i++) {
|
||||
BPose *pose = fPoseList->ItemAt(i);
|
||||
const char * text;
|
||||
if (ViewMode() == kListMode)
|
||||
text = pose->TargetModel()->Name();
|
||||
else {
|
||||
float score = -1;
|
||||
|
||||
if (ViewMode() == kListMode) {
|
||||
ModelNodeLazyOpener modelOpener(pose->TargetModel());
|
||||
BTextWidget *widget = pose->WidgetFor(firstColumn, this, modelOpener);
|
||||
if (widget)
|
||||
text = widget->Text();
|
||||
else
|
||||
text = pose->TargetModel()->Name();
|
||||
BTextWidget *widget = pose->WidgetFor(column, this, modelOpener);
|
||||
const char *text = NULL;
|
||||
if (widget != NULL)
|
||||
text = widget->Text(this);
|
||||
|
||||
if (text != NULL) {
|
||||
score = ComputeTypeAheadScore(text, sMatchString,
|
||||
matchLength);
|
||||
}
|
||||
} else {
|
||||
score = ComputeTypeAheadScore(pose->TargetModel()->Name(),
|
||||
sMatchString, matchLength);
|
||||
}
|
||||
|
||||
if (strcasecmp(text, sMatchString) >= 0)
|
||||
if (strcasecmp(text, bestSoFar) <= 0 || !bestSoFar[0]) {
|
||||
strcpy(bestSoFar, text);
|
||||
if (score > bestScore) {
|
||||
poseToSelect = pose;
|
||||
bestScore = score;
|
||||
*index = i;
|
||||
}
|
||||
if (score == kExactMatchScore)
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: we might want to change this to make it always work
|
||||
// over all columns, but this would require some more changes
|
||||
// to how Tracker represents data (for example we could filter
|
||||
// the results out).
|
||||
if (bestScore > 0 || ViewMode() != kListMode)
|
||||
break;
|
||||
}
|
||||
|
||||
return poseToSelect;
|
||||
@@ -6339,7 +6356,7 @@ BPoseView::WasClickInPath(const BPose *pose, int32 index, BPoint mouseLoc) const
|
||||
if (widget->AttrHash() != AttrHashString(kAttrPath, B_STRING_TYPE))
|
||||
return false;
|
||||
|
||||
BEntry entry(widget->Text());
|
||||
BEntry entry(widget->Text(this));
|
||||
if (entry.InitCheck() != B_OK)
|
||||
return false;
|
||||
|
||||
|
||||
@@ -92,15 +92,13 @@ BTextWidget::RecalculateText(const BPoseView *view)
|
||||
|
||||
|
||||
const char *
|
||||
BTextWidget::Text() const
|
||||
BTextWidget::Text(const BPoseView *view) const
|
||||
{
|
||||
StringAttributeText *textAttribute = dynamic_cast<StringAttributeText *>(fText);
|
||||
if (textAttribute == NULL)
|
||||
return NULL;
|
||||
|
||||
ASSERT(textAttribute);
|
||||
if (!textAttribute)
|
||||
return "";
|
||||
|
||||
return textAttribute->Value();
|
||||
return textAttribute->ValueAsText(view);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ public:
|
||||
bool IsActive() const;
|
||||
void SetActive(bool);
|
||||
|
||||
const char *Text() const;
|
||||
const char *Text(const BPoseView *view) const;
|
||||
// returns the untruncated version of the text
|
||||
float TextWidth(const BPoseView *) const;
|
||||
float PreferredWidth(const BPoseView *) const;
|
||||
|
||||
@@ -80,6 +80,8 @@ static const float kStubToStringSlotX = 5;
|
||||
|
||||
namespace BPrivate {
|
||||
|
||||
const float kExactMatchScore = INFINITY;
|
||||
|
||||
const rgb_color kBlack = {0, 0, 0, 255};
|
||||
const rgb_color kWhite = {255, 255, 255, 255};
|
||||
|
||||
@@ -1583,6 +1585,77 @@ BootedInSafeMode()
|
||||
}
|
||||
|
||||
|
||||
float
|
||||
ComputeTypeAheadScore(const char *text, const char *match, size_t matchLength,
|
||||
bool wordMode)
|
||||
{
|
||||
float first = -1;
|
||||
float second = -1;
|
||||
float third = -1;
|
||||
|
||||
// highest score: exact match
|
||||
float score = 0;
|
||||
size_t pos = 0;
|
||||
for (; pos < matchLength; pos++) {
|
||||
if (text[pos] == '\0') {
|
||||
score = 0;
|
||||
break;
|
||||
}
|
||||
if (tolower(text[pos]) != tolower(match[pos]))
|
||||
break;
|
||||
|
||||
score++;
|
||||
}
|
||||
if (pos == matchLength) {
|
||||
// we don't need to look any further
|
||||
return kExactMatchScore;
|
||||
}
|
||||
|
||||
first = score;
|
||||
|
||||
// there was no exact match
|
||||
|
||||
// second best: all characters at word beginnings
|
||||
if (wordMode) {
|
||||
score = 0;
|
||||
for (int32 j = 0, k = 0; match[j]; j++) {
|
||||
while (text[k]
|
||||
&& tolower(text[k]) != tolower(match[j])) {
|
||||
k++;
|
||||
}
|
||||
if (text[k] == '\0') {
|
||||
score = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
bool wordStart = k == 0 || isspace(text[k - 1]);
|
||||
if (wordStart)
|
||||
score++;
|
||||
if (j > 0) {
|
||||
bool wordEnd = !text[k + 1] || isspace(text[k + 1]);
|
||||
if (wordEnd)
|
||||
score += 0.3;
|
||||
if (match[j - 1] == text[k - 1])
|
||||
score += 0.7;
|
||||
}
|
||||
|
||||
score += 1.f / (k + 1);
|
||||
k++;
|
||||
}
|
||||
second = score;
|
||||
}
|
||||
|
||||
// acceptable last: exact match inside the string
|
||||
score = 0;
|
||||
const char* found = strstr(text + 1, match);
|
||||
if (found != NULL)
|
||||
score = 1.f / (found - text);
|
||||
third = score;
|
||||
|
||||
return max_c(first, max_c(second, third));
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
_ThrowOnError(status_t error, const char *DEBUG_ONLY(file), int32 DEBUG_ONLY(line))
|
||||
{
|
||||
|
||||
@@ -616,6 +616,10 @@ inline int64 SwapInt64(int64 value) { return (int64)B_SWAP_INT64((uint64)value);
|
||||
inline uint64 SwapUInt64(uint64 value) { return B_SWAP_INT64(value); }
|
||||
|
||||
|
||||
extern const float kExactMatchScore;
|
||||
float ComputeTypeAheadScore(const char *text, const char *match,
|
||||
size_t matchLength, bool wordMode = false);
|
||||
|
||||
} // namespace BPrivate
|
||||
|
||||
#endif
|
||||
#endif // _UTILITIES_H
|
||||
|
||||
@@ -520,7 +520,8 @@ WidgetAttributeText::SetDirty(bool value)
|
||||
// #pragma mark -
|
||||
|
||||
|
||||
StringAttributeText::StringAttributeText(const Model *model, const BColumn *column)
|
||||
StringAttributeText::StringAttributeText(const Model *model,
|
||||
const BColumn *column)
|
||||
: WidgetAttributeText(model, column),
|
||||
fValueDirty(true)
|
||||
{
|
||||
@@ -528,7 +529,7 @@ StringAttributeText::StringAttributeText(const Model *model, const BColumn *colu
|
||||
|
||||
|
||||
const char *
|
||||
StringAttributeText::Value()
|
||||
StringAttributeText::ValueAsText(const BPoseView * /*view*/)
|
||||
{
|
||||
if (fValueDirty)
|
||||
ReadValue(&fFullValueText);
|
||||
@@ -573,7 +574,7 @@ StringAttributeText::PreferredWidth(const BPoseView *pose) const
|
||||
|
||||
|
||||
int
|
||||
StringAttributeText::Compare(WidgetAttributeText &attr, BPoseView *)
|
||||
StringAttributeText::Compare(WidgetAttributeText &attr, BPoseView *view)
|
||||
{
|
||||
StringAttributeText *compareTo =
|
||||
dynamic_cast<StringAttributeText *>(&attr);
|
||||
@@ -582,7 +583,7 @@ StringAttributeText::Compare(WidgetAttributeText &attr, BPoseView *)
|
||||
if (fValueDirty)
|
||||
ReadValue(&fFullValueText);
|
||||
|
||||
return strcasecmp(fFullValueText.String(), compareTo->Value());
|
||||
return strcasecmp(fFullValueText.String(), compareTo->ValueAsText(view));
|
||||
}
|
||||
|
||||
|
||||
@@ -757,7 +758,7 @@ NameAttributeText::NameAttributeText(const Model *model, const BColumn *column)
|
||||
|
||||
|
||||
int
|
||||
NameAttributeText::Compare(WidgetAttributeText &attr, BPoseView *)
|
||||
NameAttributeText::Compare(WidgetAttributeText &attr, BPoseView *view)
|
||||
{
|
||||
NameAttributeText *compareTo = dynamic_cast<NameAttributeText *>(&attr);
|
||||
|
||||
@@ -769,7 +770,7 @@ NameAttributeText::Compare(WidgetAttributeText &attr, BPoseView *)
|
||||
if (NameAttributeText::sSortFolderNamesFirst)
|
||||
return fModel->CompareFolderNamesFirst(attr.TargetModel());
|
||||
|
||||
return strcasecmp(fFullValueText.String(), compareTo->Value());
|
||||
return strcasecmp(fFullValueText.String(), compareTo->ValueAsText(view));
|
||||
}
|
||||
|
||||
|
||||
@@ -1122,8 +1123,7 @@ ModificationTimeAttributeText::ReadValue()
|
||||
|
||||
GenericAttributeText::GenericAttributeText(const Model *model,
|
||||
const BColumn *column)
|
||||
: WidgetAttributeText(model, column),
|
||||
fValueDirty(true)
|
||||
: StringAttributeText(model, column)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1133,7 +1133,7 @@ GenericAttributeText::CheckAttributeChanged()
|
||||
{
|
||||
GenericValueStruct tmpValue = fValue;
|
||||
BString tmpString(fFullValueText);
|
||||
ReadValue();
|
||||
ReadValue(&fFullValueText);
|
||||
|
||||
// fDirty could already be true, in that case we mustn't set it to
|
||||
// false, even if the attribute text hasn't changed
|
||||
@@ -1153,7 +1153,7 @@ GenericAttributeText::PreferredWidth(const BPoseView *pose) const
|
||||
|
||||
|
||||
void
|
||||
GenericAttributeText::ReadValue()
|
||||
GenericAttributeText::ReadValue(BString *result)
|
||||
{
|
||||
BModelOpener opener(const_cast<Model *>(fModel));
|
||||
|
||||
@@ -1179,7 +1179,7 @@ GenericAttributeText::ReadValue()
|
||||
// didn't read the whole attribute in or it wasn't to
|
||||
// begin with
|
||||
|
||||
fFullValueText = buffer;
|
||||
*result = buffer;
|
||||
fValueIsDefined = true;
|
||||
}
|
||||
break;
|
||||
@@ -1270,7 +1270,7 @@ void
|
||||
GenericAttributeText::FitValue(BString *result, const BPoseView *view)
|
||||
{
|
||||
if (fValueDirty)
|
||||
ReadValue();
|
||||
ReadValue(&fFullValueText);
|
||||
|
||||
fOldWidth = fColumn->Width();
|
||||
|
||||
@@ -1411,11 +1411,25 @@ GenericAttributeText::FitValue(BString *result, const BPoseView *view)
|
||||
fDirty = false;
|
||||
return;
|
||||
}
|
||||
fTruncatedWidth = TruncString(result, buffer, (ssize_t)strlen(buffer), view, fOldWidth);
|
||||
fTruncatedWidth = TruncString(result, buffer, (ssize_t)strlen(buffer), view,
|
||||
fOldWidth);
|
||||
fDirty = false;
|
||||
}
|
||||
|
||||
|
||||
const char*
|
||||
GenericAttributeText::ValueAsText(const BPoseView *view)
|
||||
{
|
||||
// TODO: redesign this - this is to make sure the value is valid
|
||||
bool oldDirty = fDirty;
|
||||
BString result;
|
||||
FitValue(&result, view);
|
||||
fDirty = oldDirty;
|
||||
|
||||
return fFullValueText.String();
|
||||
}
|
||||
|
||||
|
||||
int
|
||||
GenericAttributeText::Compare(WidgetAttributeText &attr, BPoseView *)
|
||||
{
|
||||
@@ -1424,14 +1438,15 @@ GenericAttributeText::Compare(WidgetAttributeText &attr, BPoseView *)
|
||||
ASSERT(compareTo);
|
||||
|
||||
if (fValueDirty)
|
||||
ReadValue();
|
||||
ReadValue(&fFullValueText);
|
||||
if (compareTo->fValueDirty)
|
||||
compareTo->ReadValue();
|
||||
compareTo->ReadValue(&compareTo->fFullValueText);
|
||||
|
||||
// Sort undefined values last, regardless of the other value:
|
||||
if (fValueIsDefined == false || compareTo->fValueIsDefined == false)
|
||||
if (fValueIsDefined == false || compareTo->fValueIsDefined == false) {
|
||||
return fValueIsDefined < compareTo->fValueIsDefined ?
|
||||
(fValueIsDefined == compareTo->fValueIsDefined ? 0 : -1) : 1;
|
||||
}
|
||||
|
||||
switch (fColumn->AttrType()) {
|
||||
case B_STRING_TYPE:
|
||||
|
||||
@@ -140,9 +140,11 @@ WidgetAttributeText::TargetModel() const
|
||||
class StringAttributeText : public WidgetAttributeText {
|
||||
public:
|
||||
StringAttributeText(const Model *, const BColumn *);
|
||||
const char *Value();
|
||||
|
||||
virtual const char *ValueAsText(const BPoseView *view);
|
||||
// returns the untrucated text that corresponds to the attribute
|
||||
// value
|
||||
|
||||
virtual bool CheckAttributeChanged();
|
||||
|
||||
virtual float PreferredWidth(const BPoseView *) const;
|
||||
@@ -156,6 +158,7 @@ class StringAttributeText : public WidgetAttributeText {
|
||||
virtual void ReadValue(BString *result) = 0;
|
||||
|
||||
virtual int Compare(WidgetAttributeText &, BPoseView *view);
|
||||
|
||||
BString fFullValueText;
|
||||
bool fValueDirty;
|
||||
// used for lazy read, managed by ReadValue
|
||||
@@ -200,32 +203,32 @@ union GenericValueStruct {
|
||||
};
|
||||
|
||||
|
||||
class GenericAttributeText : public WidgetAttributeText {
|
||||
class GenericAttributeText : public StringAttributeText {
|
||||
// used for displaying mime extra attributes
|
||||
// supports different formats
|
||||
public:
|
||||
GenericAttributeText(const Model *, const BColumn *);
|
||||
GenericAttributeText(const Model *model, const BColumn *column);
|
||||
virtual bool CheckAttributeChanged();
|
||||
|
||||
virtual float PreferredWidth(const BPoseView *) const;
|
||||
virtual float PreferredWidth(const BPoseView *view) const;
|
||||
|
||||
virtual int Compare(WidgetAttributeText &, BPoseView *view);
|
||||
|
||||
virtual void SetUpEditing(BTextView *);
|
||||
virtual bool CommitEditedText(BTextView *);
|
||||
|
||||
virtual const char *ValueAsText(const BPoseView *view);
|
||||
|
||||
private:
|
||||
virtual bool CommitEditedTextFlavor(BTextView *);
|
||||
|
||||
virtual void FitValue(BString *result, const BPoseView *);
|
||||
virtual void ReadValue();
|
||||
virtual void ReadValue(BString *result);
|
||||
|
||||
// ToDo:
|
||||
// TODO:
|
||||
// split this up into a scalar flavor and string flavor
|
||||
// to save memory
|
||||
BString fFullValueText;
|
||||
GenericValueStruct fValue;
|
||||
bool fValueDirty;
|
||||
};
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user