Mail: work in progress to use the layout API.
This commit is contained in:
@@ -0,0 +1,987 @@
|
||||
/*
|
||||
* Copyright 2015, Axel Dörfler, [email protected].
|
||||
* Copyright 2010 Stephan Aßmus <[email protected]>
|
||||
* Distributed under the terms of the MIT License.
|
||||
*/
|
||||
|
||||
|
||||
#include "AddressTextControl.h"
|
||||
|
||||
#include <Autolock.h>
|
||||
#include <Button.h>
|
||||
#include <Catalog.h>
|
||||
#include <ControlLook.h>
|
||||
#include <Clipboard.h>
|
||||
#include <File.h>
|
||||
#include <LayoutBuilder.h>
|
||||
#include <Locale.h>
|
||||
#include <LayoutUtils.h>
|
||||
#include <NodeInfo.h>
|
||||
#include <PopUpMenu.h>
|
||||
#include <SeparatorView.h>
|
||||
#include <TextView.h>
|
||||
#include <Window.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "MailApp.h"
|
||||
#include "Messages.h"
|
||||
#include "QueryList.h"
|
||||
#include "TextViewCompleter.h"
|
||||
|
||||
|
||||
#undef B_TRANSLATION_CONTEXT
|
||||
#define B_TRANSLATION_CONTEXT "AddressTextControl"
|
||||
|
||||
|
||||
static const uint32 kMsgAddAddress = 'adad';
|
||||
static const float kHorizontalTextRectInset = 4.0;
|
||||
static const float kVerticalTextRectInset = 2.0;
|
||||
|
||||
|
||||
class AddressTextControl::TextView : public BTextView {
|
||||
private:
|
||||
static const uint32 MSG_CLEAR = 'cler';
|
||||
|
||||
public:
|
||||
TextView(AddressTextControl* parent);
|
||||
virtual ~TextView();
|
||||
|
||||
virtual void MessageReceived(BMessage* message);
|
||||
virtual void FrameResized(float width, float height);
|
||||
virtual void KeyDown(const char* bytes, int32 numBytes);
|
||||
virtual void MakeFocus(bool focused = true);
|
||||
|
||||
virtual BSize MinSize();
|
||||
virtual BSize MaxSize();
|
||||
|
||||
const BMessage* ModificationMessage() const;
|
||||
void SetModificationMessage(BMessage* message);
|
||||
|
||||
void SetUpdateAutoCompleterChoices(bool update);
|
||||
|
||||
protected:
|
||||
virtual void InsertText(const char* text, int32 length,
|
||||
int32 offset,
|
||||
const text_run_array* runs);
|
||||
virtual void DeleteText(int32 fromOffset, int32 toOffset);
|
||||
|
||||
private:
|
||||
void _AlignTextRect();
|
||||
|
||||
private:
|
||||
AddressTextControl* fAddressTextControl;
|
||||
TextViewCompleter* fAutoCompleter;
|
||||
BString fPreviousText;
|
||||
bool fUpdateAutoCompleterChoices;
|
||||
BMessage* fModificationMessage;
|
||||
};
|
||||
|
||||
|
||||
class AddressPopUpMenu : public BPopUpMenu, public QueryListener {
|
||||
public:
|
||||
AddressPopUpMenu();
|
||||
virtual ~AddressPopUpMenu();
|
||||
|
||||
protected:
|
||||
virtual void EntryCreated(QueryList& source,
|
||||
const entry_ref& ref, ino_t node);
|
||||
virtual void EntryRemoved(QueryList& source,
|
||||
const node_ref& nodeRef);
|
||||
|
||||
private:
|
||||
void _RebuildMenu();
|
||||
void _AddGroup(const char* label, const char* group,
|
||||
PersonList& peopleList);
|
||||
void _AddPeople(BMenu* menu, PersonList& peopleList,
|
||||
const char* group,
|
||||
bool addSeparator = false);
|
||||
bool _MatchesGroup(const Person& person,
|
||||
const char* group);
|
||||
};
|
||||
|
||||
|
||||
class AddressTextControl::PopUpButton : public BControl {
|
||||
public:
|
||||
PopUpButton();
|
||||
virtual ~PopUpButton();
|
||||
|
||||
virtual BSize MinSize();
|
||||
virtual BSize PreferredSize();
|
||||
virtual BSize MaxSize();
|
||||
|
||||
virtual void MouseDown(BPoint where);
|
||||
virtual void Draw(BRect updateRect);
|
||||
|
||||
private:
|
||||
AddressPopUpMenu* fPopUpMenu;
|
||||
};
|
||||
|
||||
|
||||
class PeopleChoiceModel : public BAutoCompleter::ChoiceModel {
|
||||
public:
|
||||
PeopleChoiceModel()
|
||||
:
|
||||
fChoices(5, true)
|
||||
{
|
||||
}
|
||||
|
||||
~PeopleChoiceModel()
|
||||
{
|
||||
}
|
||||
|
||||
virtual void FetchChoicesFor(const BString& pattern)
|
||||
{
|
||||
// Remove all existing choices
|
||||
fChoices.MakeEmpty();
|
||||
|
||||
// Search through the people list for any matches
|
||||
PersonList& peopleList = static_cast<TMailApp*>(be_app)->People();
|
||||
BAutolock locker(peopleList);
|
||||
|
||||
for (int32 index = 0; index < peopleList.CountPersons(); index++) {
|
||||
const Person* person = peopleList.PersonAt(index);
|
||||
|
||||
const BString& baseText = person->Name();
|
||||
for (int32 addressIndex = 0;
|
||||
addressIndex < person->CountAddresses(); addressIndex++) {
|
||||
BString choiceText = baseText;
|
||||
choiceText << " <" << person->AddressAt(addressIndex) << ">";
|
||||
|
||||
int32 match = choiceText.IFindFirst(pattern);
|
||||
if (match < 0)
|
||||
continue;
|
||||
|
||||
fChoices.AddItem(new BAutoCompleter::Choice(choiceText,
|
||||
choiceText, match, pattern.Length()));
|
||||
}
|
||||
}
|
||||
|
||||
locker.Unlock();
|
||||
fChoices.SortItems(_CompareChoices);
|
||||
}
|
||||
|
||||
virtual int32 CountChoices() const
|
||||
{
|
||||
return fChoices.CountItems();
|
||||
}
|
||||
|
||||
virtual const BAutoCompleter::Choice* ChoiceAt(int32 index) const
|
||||
{
|
||||
return fChoices.ItemAt(index);
|
||||
}
|
||||
|
||||
static int _CompareChoices(const BAutoCompleter::Choice* a,
|
||||
const BAutoCompleter::Choice* b)
|
||||
{
|
||||
return a->DisplayText().Compare(b->DisplayText());
|
||||
}
|
||||
|
||||
private:
|
||||
BObjectList<BAutoCompleter::Choice> fChoices;
|
||||
};
|
||||
|
||||
|
||||
// #pragma mark - TextView
|
||||
|
||||
|
||||
AddressTextControl::TextView::TextView(AddressTextControl* parent)
|
||||
:
|
||||
BTextView("mail"),
|
||||
fAddressTextControl(parent),
|
||||
fAutoCompleter(new TextViewCompleter(this,
|
||||
new PeopleChoiceModel())),
|
||||
fPreviousText(""),
|
||||
fUpdateAutoCompleterChoices(true)
|
||||
{
|
||||
MakeResizable(true);
|
||||
SetStylable(true);
|
||||
fAutoCompleter->SetModificationsReported(true);
|
||||
}
|
||||
|
||||
|
||||
AddressTextControl::TextView::~TextView()
|
||||
{
|
||||
delete fAutoCompleter;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AddressTextControl::TextView::MessageReceived(BMessage* message)
|
||||
{
|
||||
switch (message->what) {
|
||||
case MSG_CLEAR:
|
||||
SetText("");
|
||||
break;
|
||||
|
||||
default:
|
||||
BTextView::MessageReceived(message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AddressTextControl::TextView::FrameResized(float width, float height)
|
||||
{
|
||||
BTextView::FrameResized(width, height);
|
||||
_AlignTextRect();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AddressTextControl::TextView::KeyDown(const char* bytes, int32 numBytes)
|
||||
{
|
||||
switch (bytes[0]) {
|
||||
case B_TAB:
|
||||
BView::KeyDown(bytes, numBytes);
|
||||
break;
|
||||
|
||||
case B_ESCAPE:
|
||||
// Revert to text as it was when we received keyboard focus.
|
||||
SetText(fPreviousText.String());
|
||||
SelectAll();
|
||||
break;
|
||||
|
||||
case B_RETURN:
|
||||
// Don't let this through to the text view.
|
||||
break;
|
||||
|
||||
default:
|
||||
BTextView::KeyDown(bytes, numBytes);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AddressTextControl::TextView::MakeFocus(bool focus)
|
||||
{
|
||||
if (focus == IsFocus())
|
||||
return;
|
||||
|
||||
BTextView::MakeFocus(focus);
|
||||
|
||||
if (focus) {
|
||||
fPreviousText = Text();
|
||||
SelectAll();
|
||||
}
|
||||
|
||||
fAddressTextControl->Invalidate();
|
||||
}
|
||||
|
||||
|
||||
BSize
|
||||
AddressTextControl::TextView::MinSize()
|
||||
{
|
||||
BSize min;
|
||||
min.height = ceilf(LineHeight(0) + kVerticalTextRectInset);
|
||||
// we always add at least one pixel vertical inset top/bottom for
|
||||
// the text rect.
|
||||
min.width = min.height * 3;
|
||||
return BLayoutUtils::ComposeSize(ExplicitMinSize(), min);
|
||||
}
|
||||
|
||||
|
||||
BSize
|
||||
AddressTextControl::TextView::MaxSize()
|
||||
{
|
||||
BSize max(MinSize());
|
||||
max.width = B_SIZE_UNLIMITED;
|
||||
return BLayoutUtils::ComposeSize(ExplicitMaxSize(), max);
|
||||
}
|
||||
|
||||
|
||||
const BMessage*
|
||||
AddressTextControl::TextView::ModificationMessage() const
|
||||
{
|
||||
return fModificationMessage;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AddressTextControl::TextView::SetModificationMessage(BMessage* message)
|
||||
{
|
||||
fModificationMessage = message;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AddressTextControl::TextView::SetUpdateAutoCompleterChoices(bool update)
|
||||
{
|
||||
fUpdateAutoCompleterChoices = update;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AddressTextControl::TextView::InsertText(const char* text,
|
||||
int32 length, int32 offset, const text_run_array* runs)
|
||||
{
|
||||
if (!strncmp(text, "mailto:", 7)) {
|
||||
text += 7;
|
||||
length -= 7;
|
||||
if (runs != NULL)
|
||||
runs = NULL;
|
||||
}
|
||||
|
||||
// Filter all line breaks, note that text is not terminated.
|
||||
if (length == 1) {
|
||||
if (*text == '\n' || *text == '\r')
|
||||
BTextView::InsertText(" ", 1, offset, runs);
|
||||
else
|
||||
BTextView::InsertText(text, 1, offset, runs);
|
||||
} else {
|
||||
BString filteredText(text, length);
|
||||
filteredText.ReplaceAll('\n', ' ');
|
||||
filteredText.ReplaceAll('\r', ' ');
|
||||
BTextView::InsertText(filteredText.String(), length, offset,
|
||||
runs);
|
||||
}
|
||||
|
||||
// TODO: change E-mail representation
|
||||
/*
|
||||
// Make the base URL part bold.
|
||||
BString text(Text(), TextLength());
|
||||
int32 baseUrlStart = text.FindFirst("://");
|
||||
if (baseUrlStart >= 0)
|
||||
baseUrlStart += 3;
|
||||
else
|
||||
baseUrlStart = 0;
|
||||
int32 baseUrlEnd = text.FindFirst("/", baseUrlStart);
|
||||
if (baseUrlEnd < 0)
|
||||
baseUrlEnd = TextLength();
|
||||
|
||||
BFont font;
|
||||
GetFont(&font);
|
||||
const rgb_color black = (rgb_color) { 0, 0, 0, 255 };
|
||||
const rgb_color gray = (rgb_color) { 60, 60, 60, 255 };
|
||||
if (baseUrlStart > 0)
|
||||
SetFontAndColor(0, baseUrlStart, &font, B_FONT_ALL, &gray);
|
||||
if (baseUrlEnd > baseUrlStart) {
|
||||
font.SetFace(B_BOLD_FACE);
|
||||
SetFontAndColor(baseUrlStart, baseUrlEnd, &font, B_FONT_ALL, &black);
|
||||
}
|
||||
if (baseUrlEnd < TextLength()) {
|
||||
font.SetFace(B_REGULAR_FACE);
|
||||
SetFontAndColor(baseUrlEnd, TextLength(), &font, B_FONT_ALL, &gray);
|
||||
}
|
||||
*/
|
||||
fAutoCompleter->TextModified(fUpdateAutoCompleterChoices);
|
||||
fAddressTextControl->InvokeNotify(fModificationMessage,
|
||||
B_CONTROL_MODIFIED);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AddressTextControl::TextView::DeleteText(int32 fromOffset,
|
||||
int32 toOffset)
|
||||
{
|
||||
BTextView::DeleteText(fromOffset, toOffset);
|
||||
|
||||
fAutoCompleter->TextModified(fUpdateAutoCompleterChoices);
|
||||
fAddressTextControl->InvokeNotify(fModificationMessage,
|
||||
B_CONTROL_MODIFIED);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AddressTextControl::TextView::_AlignTextRect()
|
||||
{
|
||||
// Layout the text rect to be in the middle, normally this means there
|
||||
// is one pixel spacing on each side.
|
||||
BRect textRect(Bounds());
|
||||
textRect.left = 0.0;
|
||||
float vInset = max_c(1,
|
||||
floorf((textRect.Height() - LineHeight(0)) / 2.0 + 0.5));
|
||||
float hInset = kHorizontalTextRectInset;
|
||||
|
||||
if (be_control_look)
|
||||
hInset = be_control_look->DefaultLabelSpacing();
|
||||
|
||||
textRect.InsetBy(hInset, vInset);
|
||||
SetTextRect(textRect);
|
||||
}
|
||||
|
||||
|
||||
// #pragma mark - PopUpButton
|
||||
|
||||
|
||||
AddressTextControl::PopUpButton::PopUpButton()
|
||||
:
|
||||
BControl(NULL, NULL, NULL, B_WILL_DRAW)
|
||||
{
|
||||
fPopUpMenu = new AddressPopUpMenu();
|
||||
}
|
||||
|
||||
|
||||
AddressTextControl::PopUpButton::~PopUpButton()
|
||||
{
|
||||
delete fPopUpMenu;
|
||||
}
|
||||
|
||||
|
||||
BSize
|
||||
AddressTextControl::PopUpButton::MinSize()
|
||||
{
|
||||
// TODO: BControlLook does not give us any size information!
|
||||
return BSize(10, 10);
|
||||
}
|
||||
|
||||
|
||||
BSize
|
||||
AddressTextControl::PopUpButton::PreferredSize()
|
||||
{
|
||||
return BSize(10, B_SIZE_UNSET);
|
||||
}
|
||||
|
||||
|
||||
BSize
|
||||
AddressTextControl::PopUpButton::MaxSize()
|
||||
{
|
||||
return BSize(10, B_SIZE_UNLIMITED);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AddressTextControl::PopUpButton::MouseDown(BPoint where)
|
||||
{
|
||||
if (fPopUpMenu->Parent() != NULL)
|
||||
return;
|
||||
|
||||
float width;
|
||||
fPopUpMenu->GetPreferredSize(&width, NULL);
|
||||
fPopUpMenu->SetTargetForItems(Parent());
|
||||
|
||||
BPoint point(Bounds().Width() - width, Bounds().Height() + 2);
|
||||
ConvertToScreen(&point);
|
||||
fPopUpMenu->Go(point, true, true, true);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AddressTextControl::PopUpButton::Draw(BRect updateRect)
|
||||
{
|
||||
uint32 flags = 0;
|
||||
if (!IsEnabled())
|
||||
flags |= BControlLook::B_DISABLED;
|
||||
|
||||
if (IsFocus() && Window()->IsActive())
|
||||
flags |= BControlLook::B_FOCUSED;
|
||||
|
||||
rgb_color base = ui_color(B_MENU_BACKGROUND_COLOR);
|
||||
BRect rect = Bounds();
|
||||
be_control_look->DrawMenuFieldBackground(this, rect,
|
||||
updateRect, base, true, flags);
|
||||
}
|
||||
|
||||
|
||||
// #pragma mark - PopUpMenu
|
||||
|
||||
|
||||
AddressPopUpMenu::AddressPopUpMenu()
|
||||
:
|
||||
BPopUpMenu("", true)
|
||||
{
|
||||
static_cast<TMailApp*>(be_app)->PeopleQueryList().AddListener(this);
|
||||
}
|
||||
|
||||
|
||||
AddressPopUpMenu::~AddressPopUpMenu()
|
||||
{
|
||||
static_cast<TMailApp*>(be_app)->PeopleQueryList().RemoveListener(this);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AddressPopUpMenu::EntryCreated(QueryList& source,
|
||||
const entry_ref& ref, ino_t node)
|
||||
{
|
||||
_RebuildMenu();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AddressPopUpMenu::EntryRemoved(QueryList& source,
|
||||
const node_ref& nodeRef)
|
||||
{
|
||||
_RebuildMenu();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AddressPopUpMenu::_RebuildMenu()
|
||||
{
|
||||
// Remove all items
|
||||
int32 index = CountItems();
|
||||
while (index-- > 0) {
|
||||
delete RemoveItem(index);
|
||||
}
|
||||
|
||||
// Rebuild contents
|
||||
PersonList& peopleList = static_cast<TMailApp*>(be_app)->People();
|
||||
BAutolock locker(peopleList);
|
||||
|
||||
if (peopleList.CountPersons() > 0)
|
||||
_AddGroup(B_TRANSLATE("All people"), NULL, peopleList);
|
||||
|
||||
GroupList& groupList = static_cast<TMailApp*>(be_app)->PeopleGroups();
|
||||
BAutolock groupLocker(groupList);
|
||||
|
||||
for (int32 index = 0; index < groupList.CountGroups(); index++) {
|
||||
BString group = groupList.GroupAt(index);
|
||||
_AddGroup(group, group, peopleList);
|
||||
}
|
||||
|
||||
groupLocker.Unlock();
|
||||
|
||||
_AddPeople(this, peopleList, "", true);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AddressPopUpMenu::_AddGroup(const char* label, const char* group,
|
||||
PersonList& peopleList)
|
||||
{
|
||||
BMenu* menu = new BMenu(label);
|
||||
AddItem(menu);
|
||||
menu->Superitem()->SetMessage(new BMessage(kMsgAddAddress));
|
||||
|
||||
_AddPeople(menu, peopleList, group);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AddressPopUpMenu::_AddPeople(BMenu* menu, PersonList& peopleList,
|
||||
const char* group, bool addSeparator)
|
||||
{
|
||||
for (int32 index = 0; index < peopleList.CountPersons(); index++) {
|
||||
const Person* person = peopleList.PersonAt(index);
|
||||
if (!_MatchesGroup(*person, group))
|
||||
continue;
|
||||
|
||||
if (person->CountAddresses() != 0 && addSeparator) {
|
||||
menu->AddSeparatorItem();
|
||||
addSeparator = false;
|
||||
}
|
||||
|
||||
for (int32 addressIndex = 0; addressIndex < person->CountAddresses();
|
||||
addressIndex++) {
|
||||
BString email = person->Name();
|
||||
email << " <" << person->AddressAt(addressIndex) << ">";
|
||||
|
||||
BMessage* message = new BMessage(kMsgAddAddress);
|
||||
message->AddString("email", email);
|
||||
menu->AddItem(new BMenuItem(email, message));
|
||||
|
||||
if (menu->Superitem() != NULL)
|
||||
menu->Superitem()->Message()->AddString("email", email);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
AddressPopUpMenu::_MatchesGroup(const Person& person, const char* group)
|
||||
{
|
||||
if (group == NULL)
|
||||
return true;
|
||||
|
||||
if (group[0] == '\0')
|
||||
return person.CountGroups() == 0;
|
||||
|
||||
return person.IsInGroup(group);
|
||||
}
|
||||
|
||||
|
||||
// TODO: sort lists!
|
||||
/*
|
||||
void
|
||||
AddressTextControl::PopUpMenu::_AddPersonItem(const entry_ref *ref, ino_t node, BString &name,
|
||||
BString &email, const char *attr, BMenu *groupMenu, BMenuItem *superItem)
|
||||
{
|
||||
BString label;
|
||||
BString sortKey;
|
||||
// For alphabetical order sorting, usually last name.
|
||||
|
||||
// if we have no Name, just use the email address
|
||||
if (name.Length() == 0) {
|
||||
label = email;
|
||||
sortKey = email;
|
||||
} else {
|
||||
// otherwise, pretty-format it
|
||||
label << name << " (" << email << ")";
|
||||
|
||||
// Extract the last name (last word in the name),
|
||||
// removing trailing and leading spaces.
|
||||
const char *nameStart = name.String();
|
||||
const char *string = nameStart + strlen(nameStart) - 1;
|
||||
const char *wordEnd;
|
||||
|
||||
while (string >= nameStart && isspace(*string))
|
||||
string--;
|
||||
wordEnd = string + 1; // Points to just after last word.
|
||||
while (string >= nameStart && !isspace(*string))
|
||||
string--;
|
||||
string++; // Point to first letter in the word.
|
||||
if (wordEnd > string)
|
||||
sortKey.SetTo(string, wordEnd - string);
|
||||
else // Blank name, pretend that the last name is after it.
|
||||
string = nameStart + strlen(nameStart);
|
||||
|
||||
// Append the first names to the end, so that people with the same last
|
||||
// name get sorted by first name. Note no space between the end of the
|
||||
// last name and the start of the first names, but that shouldn't
|
||||
// matter for sorting.
|
||||
sortKey.Append(nameStart, string - nameStart);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// #pragma mark - AddressTextControl
|
||||
|
||||
|
||||
AddressTextControl::AddressTextControl(const char* name, BMessage* message)
|
||||
:
|
||||
BControl(name, NULL, message, B_WILL_DRAW),
|
||||
fRefDropMenu(NULL),
|
||||
fWindowActive(false),
|
||||
fEditable(true)
|
||||
{
|
||||
fTextView = new TextView(this);
|
||||
fTextView->SetExplicitMinSize(BSize(100, B_SIZE_UNSET));
|
||||
fPopUpButton = new PopUpButton();
|
||||
|
||||
BLayoutBuilder::Group<>(this, B_HORIZONTAL, 0)
|
||||
.SetInsets(2)
|
||||
.Add(fTextView)
|
||||
.Add(fPopUpButton);
|
||||
|
||||
SetFlags(Flags() | B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE);
|
||||
SetLowColor(ViewColor());
|
||||
SetViewColor(fTextView->ViewColor());
|
||||
|
||||
SetExplicitAlignment(BAlignment(B_ALIGN_USE_FULL_WIDTH,
|
||||
B_ALIGN_VERTICAL_CENTER));
|
||||
}
|
||||
|
||||
|
||||
AddressTextControl::~AddressTextControl()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AddressTextControl::AttachedToWindow()
|
||||
{
|
||||
BControl::AttachedToWindow();
|
||||
fWindowActive = Window()->IsActive();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AddressTextControl::WindowActivated(bool active)
|
||||
{
|
||||
BControl::WindowActivated(active);
|
||||
if (fWindowActive != active) {
|
||||
fWindowActive = active;
|
||||
Invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AddressTextControl::Draw(BRect updateRect)
|
||||
{
|
||||
BRect bounds(Bounds());
|
||||
rgb_color base(LowColor());
|
||||
uint32 flags = 0;
|
||||
if (!IsEnabled())
|
||||
flags |= BControlLook::B_DISABLED;
|
||||
if (fWindowActive && fTextView->IsFocus())
|
||||
flags |= BControlLook::B_FOCUSED;
|
||||
be_control_look->DrawTextControlBorder(this, bounds, updateRect, base,
|
||||
flags);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AddressTextControl::MakeFocus(bool focus)
|
||||
{
|
||||
// Forward this to the text view, we never accept focus ourselves.
|
||||
fTextView->MakeFocus(focus);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AddressTextControl::SetEnabled(bool enabled)
|
||||
{
|
||||
BControl::SetEnabled(enabled);
|
||||
fTextView->MakeEditable(enabled && fEditable);
|
||||
if (enabled)
|
||||
fTextView->SetFlags(fTextView->Flags() | B_NAVIGABLE);
|
||||
else
|
||||
fTextView->SetFlags(fTextView->Flags() & ~B_NAVIGABLE);
|
||||
|
||||
fPopUpButton->SetEnabled(enabled);
|
||||
|
||||
_UpdateTextViewColors(enabled);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AddressTextControl::MessageReceived(BMessage* message)
|
||||
{
|
||||
switch (message->what) {
|
||||
case B_SIMPLE_DATA:
|
||||
{
|
||||
int32 buttons = -1;
|
||||
BPoint point;
|
||||
if (message->FindInt32("buttons", &buttons) != B_OK)
|
||||
buttons = B_PRIMARY_MOUSE_BUTTON;
|
||||
|
||||
if (buttons != B_PRIMARY_MOUSE_BUTTON
|
||||
&& message->FindPoint("_drop_point_", &point) != B_OK)
|
||||
return;
|
||||
|
||||
BMessage forwardRefs(B_REFS_RECEIVED);
|
||||
bool forward = false;
|
||||
|
||||
entry_ref ref;
|
||||
for (int32 index = 0;message->FindRef("refs", index, &ref) == B_OK; index++) {
|
||||
BFile file(&ref, B_READ_ONLY);
|
||||
if (file.InitCheck() == B_NO_ERROR) {
|
||||
BNodeInfo info(&file);
|
||||
char type[B_FILE_NAME_LENGTH];
|
||||
info.GetType(type);
|
||||
|
||||
if (!strcmp(type,"application/x-person")) {
|
||||
// add person's E-mail address to the To: field
|
||||
|
||||
BString attr = "";
|
||||
if (buttons == B_PRIMARY_MOUSE_BUTTON) {
|
||||
if (message->FindString("attr", &attr) < B_OK)
|
||||
attr = "META:email";
|
||||
} else {
|
||||
BNode node(&ref);
|
||||
node.RewindAttrs();
|
||||
|
||||
char buffer[B_ATTR_NAME_LENGTH];
|
||||
|
||||
delete fRefDropMenu;
|
||||
fRefDropMenu = new BPopUpMenu("RecipientMenu");
|
||||
|
||||
while (node.GetNextAttrName(buffer) == B_OK) {
|
||||
if (strstr(buffer, "email") <= 0)
|
||||
continue;
|
||||
|
||||
attr = buffer;
|
||||
|
||||
BString address;
|
||||
node.ReadAttrString(buffer, &address);
|
||||
if (address.Length() <= 0)
|
||||
continue;
|
||||
|
||||
BMessage* itemMsg
|
||||
= new BMessage(kMsgAddAddress);
|
||||
itemMsg->AddString("email", address.String());
|
||||
|
||||
BMenuItem* item = new BMenuItem(
|
||||
address.String(), itemMsg);
|
||||
fRefDropMenu->AddItem(item);
|
||||
}
|
||||
|
||||
if (fRefDropMenu->CountItems() > 1) {
|
||||
fRefDropMenu->SetTargetForItems(this);
|
||||
fRefDropMenu->Go(point, true, true, true);
|
||||
return;
|
||||
} else {
|
||||
delete fRefDropMenu;
|
||||
fRefDropMenu = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
BString email;
|
||||
file.ReadAttrString(attr.String(), &email);
|
||||
|
||||
// we got something...
|
||||
if (email.Length() > 0) {
|
||||
// see if we can get a username as well
|
||||
BString name;
|
||||
file.ReadAttrString("META:name", &name);
|
||||
|
||||
BString address;
|
||||
if (name.Length() == 0) {
|
||||
// if we have no Name, just use the email address
|
||||
address = email;
|
||||
} else {
|
||||
// otherwise, pretty-format it
|
||||
address << "\"" << name << "\" <" << email << ">";
|
||||
}
|
||||
|
||||
_AddAddress(address);
|
||||
}
|
||||
} else {
|
||||
forward = true;
|
||||
forwardRefs.AddRef("refs", &ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (forward) {
|
||||
// Pass on to parent
|
||||
Window()->PostMessage(&forwardRefs, Parent());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case M_SELECT:
|
||||
{
|
||||
BTextView *textView = (BTextView *)ChildAt(0);
|
||||
if (textView != NULL)
|
||||
textView->Select(0, textView->TextLength());
|
||||
break;
|
||||
}
|
||||
|
||||
case kMsgAddAddress:
|
||||
{
|
||||
const char* email;
|
||||
for (int32 index = 0;
|
||||
message->FindString("email", index++, &email) == B_OK;)
|
||||
_AddAddress(email);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
BControl::MessageReceived(message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const BMessage*
|
||||
AddressTextControl::ModificationMessage() const
|
||||
{
|
||||
return fTextView->ModificationMessage();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AddressTextControl::SetModificationMessage(BMessage* message)
|
||||
{
|
||||
fTextView->SetModificationMessage(message);
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
AddressTextControl::IsEditable() const
|
||||
{
|
||||
return fEditable;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AddressTextControl::SetEditable(bool editable)
|
||||
{
|
||||
fTextView->MakeEditable(IsEnabled() && editable);
|
||||
fEditable = editable;
|
||||
|
||||
if (editable && fPopUpButton->IsHidden(this))
|
||||
fPopUpButton->Show();
|
||||
else if (!editable && !fPopUpButton->IsHidden(this))
|
||||
fPopUpButton->Hide();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AddressTextControl::SetText(const char* text)
|
||||
{
|
||||
if (text == NULL || Text() == NULL || strcmp(Text(), text) != 0) {
|
||||
fTextView->SetUpdateAutoCompleterChoices(false);
|
||||
fTextView->SetText(text);
|
||||
fTextView->SetUpdateAutoCompleterChoices(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const char*
|
||||
AddressTextControl::Text() const
|
||||
{
|
||||
return fTextView->Text();
|
||||
}
|
||||
|
||||
|
||||
int32
|
||||
AddressTextControl::TextLength() const
|
||||
{
|
||||
return fTextView->TextLength();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AddressTextControl::GetSelection(int32* start, int32* end) const
|
||||
{
|
||||
fTextView->GetSelection(start, end);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AddressTextControl::Select(int32 start, int32 end)
|
||||
{
|
||||
fTextView->Select(start, end);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AddressTextControl::SelectAll()
|
||||
{
|
||||
fTextView->Select(0, TextLength());
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
AddressTextControl::HasFocus()
|
||||
{
|
||||
return fTextView->IsFocus();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AddressTextControl::_AddAddress(const char* text)
|
||||
{
|
||||
int last = fTextView->TextLength();
|
||||
if (last != 0) {
|
||||
fTextView->Select(last, last);
|
||||
// TODO: test if there is already a ','
|
||||
fTextView->Insert(", ");
|
||||
}
|
||||
fTextView->Insert(text);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
AddressTextControl::_UpdateTextViewColors(bool enabled)
|
||||
{
|
||||
BFont font;
|
||||
fTextView->GetFontAndColor(0, &font);
|
||||
|
||||
rgb_color textColor;
|
||||
if (enabled)
|
||||
textColor = ui_color(B_DOCUMENT_TEXT_COLOR);
|
||||
else {
|
||||
textColor = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
|
||||
B_DISABLED_LABEL_TINT);
|
||||
}
|
||||
|
||||
fTextView->SetFontAndColor(&font, B_FONT_ALL, &textColor);
|
||||
|
||||
rgb_color color;
|
||||
if (enabled)
|
||||
color = ui_color(B_DOCUMENT_BACKGROUND_COLOR);
|
||||
else {
|
||||
color = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
|
||||
B_LIGHTEN_2_TINT);
|
||||
}
|
||||
|
||||
fTextView->SetViewColor(color);
|
||||
fTextView->SetLowColor(color);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2015, Axel Dörfler, [email protected].
|
||||
* Distributed under the terms of the MIT License.
|
||||
*/
|
||||
#ifndef ADDRESS_TEXT_CONTROL_H
|
||||
#define ADDRESS_TEXT_CONTROL_H
|
||||
|
||||
|
||||
#include <Control.h>
|
||||
|
||||
|
||||
class BButton;
|
||||
class BPopUpMenu;
|
||||
class BTextView;
|
||||
|
||||
|
||||
class AddressTextControl : public BControl {
|
||||
public:
|
||||
AddressTextControl(const char* name,
|
||||
BMessage* message);
|
||||
virtual ~AddressTextControl();
|
||||
|
||||
virtual void AttachedToWindow();
|
||||
virtual void WindowActivated(bool active);
|
||||
virtual void Draw(BRect updateRect);
|
||||
virtual void MakeFocus(bool focus = true);
|
||||
virtual void SetEnabled(bool enabled);
|
||||
virtual void MessageReceived(BMessage* message);
|
||||
|
||||
const BMessage* ModificationMessage() const;
|
||||
void SetModificationMessage(BMessage* message);
|
||||
|
||||
bool IsEditable() const;
|
||||
void SetEditable(bool editable);
|
||||
|
||||
void SetText(const char* text);
|
||||
const char* Text() const;
|
||||
int32 TextLength() const;
|
||||
void GetSelection(int32* start, int32* end) const;
|
||||
void Select(int32 start, int32 end);
|
||||
void SelectAll();
|
||||
|
||||
bool HasFocus();
|
||||
|
||||
private:
|
||||
void _AddAddress(const char* text);
|
||||
void _UpdateTextViewColors(bool enabled);
|
||||
|
||||
private:
|
||||
class TextView;
|
||||
class PopUpButton;
|
||||
|
||||
TextView* fTextView;
|
||||
PopUpButton* fPopUpButton;
|
||||
BPopUpMenu* fRefDropMenu;
|
||||
bool fWindowActive;
|
||||
bool fEditable;
|
||||
};
|
||||
|
||||
|
||||
#endif // ADDRESS_TEXT_CONTROL_H
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,233 +0,0 @@
|
||||
/*
|
||||
Open Tracker License
|
||||
|
||||
Terms and Conditions
|
||||
|
||||
Copyright (c) 1991-2001, 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.
|
||||
|
||||
BeMail(TM), 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.
|
||||
*/
|
||||
|
||||
//
|
||||
// ComboBox.h
|
||||
//
|
||||
// A view that is the combination of a text control and a pop-up list
|
||||
//
|
||||
//
|
||||
|
||||
#ifndef _COMBOBOX_H
|
||||
#define _COMBOBOX_H
|
||||
|
||||
#include <Control.h>
|
||||
#include <View.h>
|
||||
|
||||
class BButton;
|
||||
class BComboBox;
|
||||
class BList;
|
||||
class BTextControl;
|
||||
class BTextView;
|
||||
class BWindow;
|
||||
struct text_run_array;
|
||||
|
||||
typedef const char* (*text_input_filter_hook)(const char* inText, int32& length,
|
||||
const text_run_array*& runs);
|
||||
|
||||
/*
|
||||
// Abstract class provides an interface for BComboBox to access possible choices.
|
||||
// Choices are used for auto-completion and for showing the pop-up list
|
||||
class BChoiceList {
|
||||
public:
|
||||
// Returns the choice at index or NULL if the index is invalid
|
||||
virtual const char *ChoiceAt(int32 index) = 0;
|
||||
|
||||
// Looks for a match at or after startIndex which contains a choice
|
||||
// that starts with prefix. If a match is found, B_OK is returned,
|
||||
// matchIndex is set to the list index that should be selected, and completionText
|
||||
// is set to point at the text that should be appended to the text input.
|
||||
// If no match is found, a negative value is returned.
|
||||
virtual status_t GetMatch(const char *prefix, int32 startIndex,
|
||||
int32 *matchIndex, const char **completionText) = 0;
|
||||
|
||||
// Returns the number of choices
|
||||
virtual int32 CountChoices() = 0;
|
||||
};
|
||||
*/
|
||||
|
||||
class StringObjectList;
|
||||
|
||||
// Implementation of BChoiceList. Keeps copies of each choice added, and frees
|
||||
// the memory when the choices are removed.
|
||||
class BDefaultChoiceList // : public BChoiceList
|
||||
{
|
||||
public:
|
||||
BDefaultChoiceList(BComboBox *owner = NULL);
|
||||
virtual ~BDefaultChoiceList();
|
||||
|
||||
virtual const char *ChoiceAt(int32 index);
|
||||
virtual status_t GetMatch(const char *prefix, int32 startIndex,
|
||||
int32 *matchIndex, const char **completionText);
|
||||
virtual int32 CountChoices();
|
||||
|
||||
status_t AddChoice(const char *toAdd);
|
||||
status_t AddChoiceAt(const char *toAdd, int32 index);
|
||||
status_t RemoveChoice(const char *toRemove);
|
||||
status_t RemoveChoiceAt(int32 index);
|
||||
|
||||
void SetOwner(BComboBox *owner);
|
||||
BComboBox *Owner();
|
||||
|
||||
private:
|
||||
StringObjectList *fList;
|
||||
BComboBox *fOwner;
|
||||
};
|
||||
typedef BDefaultChoiceList BChoiceList;
|
||||
|
||||
|
||||
class BComboBox : public BControl {
|
||||
public:
|
||||
BComboBox(BRect frame, const char *name, const char *label,
|
||||
BMessage *message, uint32 resizeMask = B_FOLLOW_LEFT | B_FOLLOW_TOP,
|
||||
uint32 flags = B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE);
|
||||
// BComboBox(BMessage *data);
|
||||
virtual ~BComboBox();
|
||||
|
||||
// BArchivable methods
|
||||
// static BArchivable *Instantiate(BMessage *data);
|
||||
// virtual status_t Archive(BMessage *data, bool deep = true) const;
|
||||
|
||||
// SetChoiceList causes the BComboBox to delete the old BChoiceList object,
|
||||
// take ownership of the new choice list, and then invalidate the pop-up list
|
||||
void SetChoiceList(BChoiceList *list);
|
||||
|
||||
// ChoiceList returns a pointer to the current choice list
|
||||
BChoiceList *ChoiceList();
|
||||
|
||||
// ChoiceListUpdated should be called whenever an item in the choice list changes
|
||||
// so that BComboBox can perform the proper updating.
|
||||
virtual void ChoiceListUpdated();
|
||||
|
||||
// Select changes the list selection to the specified index, and if the
|
||||
// changeTextSelection flag is true, changes the text in the TextView to
|
||||
// the value at index and selects all the text in the TextView.
|
||||
virtual void Select(int32 index, bool changeTextSelection = false);
|
||||
|
||||
virtual void Deselect();
|
||||
|
||||
// Returns the index of the current selection, or a negative value
|
||||
int32 CurrentSelection();
|
||||
|
||||
// SetAutoComplete enables or disables auto-completion
|
||||
virtual void SetAutoComplete(bool on);
|
||||
bool GetAutoComplete();
|
||||
|
||||
// The following methods are mostly identical to their BTextControl counterparts
|
||||
virtual void SetValue(int32 value);
|
||||
virtual void SetEnabled(bool enabled);
|
||||
virtual void SetLabel(const char *text);
|
||||
virtual void SetText(const char *text);
|
||||
const char *Text() const;
|
||||
int32 TextLength() const;
|
||||
BTextView *TextView();
|
||||
virtual void SetDivider(float dividing_line);
|
||||
float Divider() const;
|
||||
virtual void SetAlignment(alignment label, alignment text);
|
||||
void GetAlignment(alignment *label, alignment *text) const;
|
||||
|
||||
virtual void SetModificationMessage(BMessage *message);
|
||||
BMessage *ModificationMessage() const;
|
||||
|
||||
void SetFilter(text_input_filter_hook hook);
|
||||
|
||||
virtual void GetPreferredSize(float *width, float *height);
|
||||
virtual void ResizeToPreferred();
|
||||
virtual void FrameMoved(BPoint new_position);
|
||||
virtual void FrameResized(float new_width, float new_height);
|
||||
virtual void WindowActivated(bool active);
|
||||
virtual void MakeFocus(bool state);
|
||||
|
||||
virtual void Draw(BRect update);
|
||||
virtual void MessageReceived(BMessage *msg);
|
||||
virtual void MouseDown(BPoint where);
|
||||
virtual void MouseUp(BPoint where);
|
||||
virtual void MouseMoved(BPoint where, uint32 transit,
|
||||
const BMessage *dragMessage);
|
||||
// virtual void AllAttached();
|
||||
virtual void AttachedToWindow();
|
||||
virtual void DetachedFromWindow();
|
||||
virtual void SetFlags(uint32 flags);
|
||||
// virtual void SetFont(const BFont *font, uint32 properties = B_FONT_ALL);
|
||||
|
||||
virtual status_t Invoke(BMessage *msg = NULL);
|
||||
|
||||
// virtual BHandler *ResolveSpecifier(BMessage *msg, int32 index,
|
||||
// BMessage *specifier, int32 form, const char *property);
|
||||
// virtual status_t GetSupportedSuites(BMessage *data);
|
||||
//
|
||||
//
|
||||
// virtual status_t Perform(perform_code d, void *arg);
|
||||
|
||||
private:
|
||||
class ComboBoxWindow;
|
||||
class ChoiceListView;
|
||||
class TextInput;
|
||||
class MovedMessageFilter;
|
||||
|
||||
protected:
|
||||
ComboBoxWindow *CreatePopupWindow();
|
||||
void CommitValue();
|
||||
void TryAutoComplete();
|
||||
void ShowPopupWindow();
|
||||
void HidePopupWindow();
|
||||
|
||||
BRect fButtonRect;
|
||||
int32 fSelected;
|
||||
int32 fCompletionIndex;
|
||||
float fDivider;
|
||||
TextInput *fText;
|
||||
ComboBoxWindow *fPopupWindow;
|
||||
BMessage *fModificationMessage;
|
||||
BChoiceList *fChoiceList;
|
||||
alignment fLabelAlign;
|
||||
bool fAutoComplete;
|
||||
bool fButtonDepressed;
|
||||
bool fDepressedWhenClicked;
|
||||
bool fTrackingButtonDown;
|
||||
|
||||
/*----- Private or reserved -----------------------------------------*/
|
||||
private:
|
||||
BRect fFrameCache;
|
||||
MovedMessageFilter *fWinMovedFilter;
|
||||
int32 fTextEnd;
|
||||
bool fSkipSetFlags;
|
||||
|
||||
friend class ChoiceListView;
|
||||
friend class ComboBoxWindow;
|
||||
friend class TextInput;
|
||||
};
|
||||
|
||||
#endif // #ifndef _COMBOBOX_H
|
||||
|
||||
+89
-105
@@ -635,38 +635,106 @@ TextRunArray::~TextRunArray()
|
||||
}
|
||||
|
||||
|
||||
//====================================================================
|
||||
// #pragma mark -
|
||||
|
||||
|
||||
TContentView::TContentView(BRect rect, bool incoming, BFont* font,
|
||||
TContentView::TContentView(bool incoming, BFont* font,
|
||||
bool showHeader, bool coloredQuotes)
|
||||
:
|
||||
BView(rect, "m_content", B_FOLLOW_ALL,
|
||||
B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE),
|
||||
|
||||
BView("m_content", B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE),
|
||||
fFocus(false),
|
||||
fIncoming(incoming)
|
||||
{
|
||||
SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
|
||||
fOffset = 12;
|
||||
|
||||
BRect r(rect);
|
||||
r.OffsetTo(0, 0);
|
||||
r.right -= B_V_SCROLL_BAR_WIDTH;
|
||||
r.bottom -= B_H_SCROLL_BAR_HEIGHT;
|
||||
r.top += 4;
|
||||
BRect text(r);
|
||||
text.OffsetTo(0, 0);
|
||||
text.InsetBy(5, 5);
|
||||
BGroupLayout* layout = new BGroupLayout(B_VERTICAL, 0);
|
||||
// TODO: control look should give us the spacing information
|
||||
layout->SetInsets(-2, 0, -2, -2);
|
||||
SetLayout(layout);
|
||||
|
||||
fTextView = new TTextView(r, text, fIncoming, this, font, showHeader,
|
||||
fTextView = new TTextView(fIncoming, this, font, showHeader,
|
||||
coloredQuotes);
|
||||
BScrollView *scroll = new BScrollView("", fTextView, B_FOLLOW_ALL, 0, true, true);
|
||||
BScrollView *scroll = new BScrollView("", fTextView, 0, true, true);
|
||||
AddChild(scroll);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
TContentView::FindString(const char *str)
|
||||
{
|
||||
int32 finish;
|
||||
int32 pass = 0;
|
||||
int32 start = 0;
|
||||
|
||||
if (str == NULL)
|
||||
return;
|
||||
|
||||
//
|
||||
// Start from current selection or from the beginning of the pool
|
||||
//
|
||||
const char *text = fTextView->Text();
|
||||
int32 count = fTextView->TextLength();
|
||||
fTextView->GetSelection(&start, &finish);
|
||||
if (start != finish)
|
||||
start = finish;
|
||||
if (!count || text == NULL)
|
||||
return;
|
||||
|
||||
//
|
||||
// Do the find
|
||||
//
|
||||
while (pass < 2) {
|
||||
long found = -1;
|
||||
char lc = tolower(str[0]);
|
||||
char uc = toupper(str[0]);
|
||||
for (long i = start; i < count; i++) {
|
||||
if (text[i] == lc || text[i] == uc) {
|
||||
const char *s = str;
|
||||
const char *t = text + i;
|
||||
while (*s && (tolower(*s) == tolower(*t))) {
|
||||
s++;
|
||||
t++;
|
||||
}
|
||||
if (*s == 0) {
|
||||
found = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Select the text if it worked
|
||||
//
|
||||
if (found != -1) {
|
||||
Window()->Activate();
|
||||
fTextView->Select(found, found + strlen(str));
|
||||
fTextView->ScrollToSelection();
|
||||
fTextView->MakeFocus(true);
|
||||
return;
|
||||
}
|
||||
else if (start) {
|
||||
start = 0;
|
||||
text = fTextView->Text();
|
||||
count = fTextView->TextLength();
|
||||
pass++;
|
||||
} else {
|
||||
beep();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
TContentView::Focus(bool focus)
|
||||
{
|
||||
if (fFocus != focus) {
|
||||
fFocus = focus;
|
||||
Draw(Frame());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
TContentView::MessageReceived(BMessage *msg)
|
||||
{
|
||||
@@ -769,100 +837,13 @@ TContentView::MessageReceived(BMessage *msg)
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
TContentView::FindString(const char *str)
|
||||
{
|
||||
int32 finish;
|
||||
int32 pass = 0;
|
||||
int32 start = 0;
|
||||
|
||||
if (str == NULL)
|
||||
return;
|
||||
|
||||
//
|
||||
// Start from current selection or from the beginning of the pool
|
||||
//
|
||||
const char *text = fTextView->Text();
|
||||
int32 count = fTextView->TextLength();
|
||||
fTextView->GetSelection(&start, &finish);
|
||||
if (start != finish)
|
||||
start = finish;
|
||||
if (!count || text == NULL)
|
||||
return;
|
||||
|
||||
//
|
||||
// Do the find
|
||||
//
|
||||
while (pass < 2) {
|
||||
long found = -1;
|
||||
char lc = tolower(str[0]);
|
||||
char uc = toupper(str[0]);
|
||||
for (long i = start; i < count; i++) {
|
||||
if (text[i] == lc || text[i] == uc) {
|
||||
const char *s = str;
|
||||
const char *t = text + i;
|
||||
while (*s && (tolower(*s) == tolower(*t))) {
|
||||
s++;
|
||||
t++;
|
||||
}
|
||||
if (*s == 0) {
|
||||
found = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Select the text if it worked
|
||||
//
|
||||
if (found != -1) {
|
||||
Window()->Activate();
|
||||
fTextView->Select(found, found + strlen(str));
|
||||
fTextView->ScrollToSelection();
|
||||
fTextView->MakeFocus(true);
|
||||
return;
|
||||
}
|
||||
else if (start) {
|
||||
start = 0;
|
||||
text = fTextView->Text();
|
||||
count = fTextView->TextLength();
|
||||
pass++;
|
||||
} else {
|
||||
beep();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
TContentView::Focus(bool focus)
|
||||
{
|
||||
if (fFocus != focus) {
|
||||
fFocus = focus;
|
||||
Draw(Frame());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
TContentView::FrameResized(float /* width */, float /* height */)
|
||||
{
|
||||
BRect r(fTextView->Bounds());
|
||||
r.OffsetTo(0, 0);
|
||||
r.InsetBy(5, 5);
|
||||
fTextView->SetTextRect(r);
|
||||
}
|
||||
|
||||
|
||||
//====================================================================
|
||||
// #pragma mark -
|
||||
|
||||
|
||||
TTextView::TTextView(BRect frame, BRect text, bool incoming, TContentView *view,
|
||||
TTextView::TTextView(bool incoming, TContentView *view,
|
||||
BFont *font, bool showHeader, bool coloredQuotes)
|
||||
:
|
||||
BTextView(frame, "", text, B_FOLLOW_ALL, B_WILL_DRAW | B_NAVIGABLE),
|
||||
BTextView("", B_WILL_DRAW | B_NAVIGABLE),
|
||||
|
||||
fHeader(showHeader),
|
||||
fColoredQuotes(coloredQuotes),
|
||||
@@ -883,6 +864,9 @@ TTextView::TTextView(BRect frame, BRect text, bool incoming, TContentView *view,
|
||||
{
|
||||
fStopSem = create_sem(1, "reader_sem");
|
||||
SetStylable(true);
|
||||
SetInsets(4, 4, 4, 4);
|
||||
// TODO: have some font size related value here
|
||||
// (ideally the same as in BTextControl, etc. from BControlLook)
|
||||
|
||||
fEnclosures = new BList();
|
||||
|
||||
|
||||
+16
-17
@@ -102,26 +102,25 @@ struct hyper_text {
|
||||
class TSavePanel;
|
||||
|
||||
|
||||
//====================================================================
|
||||
|
||||
class TContentView : public BView {
|
||||
public:
|
||||
TContentView(BRect, bool incoming, BFont*,
|
||||
bool showHeader, bool coloredQuotes);
|
||||
virtual void MessageReceived(BMessage *);
|
||||
void FindString(const char *);
|
||||
void Focus(bool);
|
||||
void FrameResized(float, float);
|
||||
public:
|
||||
TContentView(bool incoming, BFont* font,
|
||||
bool showHeader, bool coloredQuotes);
|
||||
|
||||
TTextView *fTextView;
|
||||
void FindString(const char *);
|
||||
void Focus(bool);
|
||||
|
||||
private:
|
||||
bool fFocus;
|
||||
bool fIncoming;
|
||||
float fOffset;
|
||||
TTextView* TextView() const { return fTextView; }
|
||||
|
||||
virtual void MessageReceived(BMessage* message);
|
||||
|
||||
private:
|
||||
TTextView* fTextView;
|
||||
bool fFocus;
|
||||
bool fIncoming;
|
||||
float fOffset;
|
||||
};
|
||||
|
||||
//====================================================================
|
||||
|
||||
enum {
|
||||
S_CLEAR_ERRORS = 1,
|
||||
@@ -148,7 +147,7 @@ struct quote_context {
|
||||
|
||||
class TTextView : public BTextView {
|
||||
public:
|
||||
TTextView(BRect, BRect, bool incoming,
|
||||
TTextView(bool incoming,
|
||||
TContentView*, BFont*, bool showHeader,
|
||||
bool coloredQuotes);
|
||||
~TTextView();
|
||||
@@ -283,7 +282,7 @@ class TSavePanel : public BFilePanel {
|
||||
TSavePanel(hyper_text*, TTextView*);
|
||||
virtual void SendMessage(const BMessenger*, BMessage*);
|
||||
void SetEnclosure(hyper_text*);
|
||||
|
||||
|
||||
private:
|
||||
hyper_text *fEnclosure;
|
||||
TTextView *fView;
|
||||
|
||||
+364
-1051
File diff suppressed because it is too large
Load Diff
+49
-75
@@ -35,103 +35,77 @@ All rights reserved.
|
||||
#define _HEADER_H
|
||||
|
||||
|
||||
#include "ComboBox.h"
|
||||
|
||||
#include <Box.h>
|
||||
#include <GridView.h>
|
||||
#include <NodeInfo.h>
|
||||
#include <Point.h>
|
||||
#include <Rect.h>
|
||||
#include <TextControl.h>
|
||||
#include <View.h>
|
||||
#include <Window.h>
|
||||
#include <fs_attr.h>
|
||||
|
||||
#include "AddressTextControl.h"
|
||||
|
||||
|
||||
#define TO_FIELD_H 39
|
||||
#define FROM_FIELD_H 31
|
||||
#define TO_FIELD_V 7
|
||||
#define TO_FIELD_WIDTH 270
|
||||
#define FROM_FIELD_WIDTH 280
|
||||
|
||||
#define ACCOUNT_FIELD_WIDTH 165
|
||||
|
||||
#define SUBJECT_FIELD_H 18
|
||||
#define SUBJECT_FIELD_V 33
|
||||
#define SUBJECT_FIELD_WIDTH 270
|
||||
#define SUBJECT_FIELD_HEIGHT 16
|
||||
|
||||
#define CC_FIELD_H 40
|
||||
#define CC_FIELD_V 58
|
||||
#define CC_FIELD_WIDTH 192
|
||||
#define CC_FIELD_HEIGHT 16
|
||||
|
||||
#define BCC_FIELD_H 268
|
||||
#define BCC_FIELD_V 58
|
||||
#define BCC_FIELD_WIDTH 197
|
||||
#define BCC_FIELD_HEIGHT 16
|
||||
|
||||
class BEmailMessage;
|
||||
class BFile;
|
||||
class BMenuField;
|
||||
class BMenuItem;
|
||||
class BPopUpMenu;
|
||||
class BStringView;
|
||||
class QPopupMenu;
|
||||
class TTextControl;
|
||||
class LabelView;
|
||||
|
||||
|
||||
class THeaderView : public BBox {
|
||||
class THeaderView : public BGridView {
|
||||
public:
|
||||
THeaderView(BRect, BRect, bool incoming,
|
||||
bool resending, uint32 defaultCharacterSet,
|
||||
int32 defaultAccount);
|
||||
THeaderView(bool incoming, bool resending,
|
||||
int32 defaultAccount);
|
||||
|
||||
virtual void MessageReceived(BMessage*);
|
||||
virtual void AttachedToWindow();
|
||||
status_t LoadMessage(BEmailMessage*);
|
||||
const char* From() const;
|
||||
void SetFrom(const char* from);
|
||||
|
||||
BPopUpMenu* fAccountMenu;
|
||||
BPopUpMenu* fEncodingMenu;
|
||||
int32 fAccountID;
|
||||
TTextControl* fAccountTo;
|
||||
TTextControl* fAccount;
|
||||
TTextControl* fBcc;
|
||||
TTextControl* fCc;
|
||||
TTextControl* fSubject;
|
||||
TTextControl* fTo;
|
||||
BStringView* fDateLabel;
|
||||
BStringView* fDate;
|
||||
bool fIncoming;
|
||||
uint32 fCharacterSetUserSees;
|
||||
AddressTextControl* ToControl() const
|
||||
{ return fToControl; }
|
||||
bool IsToEmpty() const;
|
||||
const char* To() const;
|
||||
void SetTo(const char* to);
|
||||
|
||||
private:
|
||||
void InitEmailCompletion();
|
||||
void InitGroupCompletion();
|
||||
bool IsCcEmpty() const;
|
||||
const char* Cc() const;
|
||||
void SetCc(const char* cc);
|
||||
|
||||
bool fResending;
|
||||
QPopupMenu* fBccMenu;
|
||||
QPopupMenu* fCcMenu;
|
||||
QPopupMenu* fToMenu;
|
||||
BDefaultChoiceList fEmailList;
|
||||
};
|
||||
bool IsBccEmpty() const;
|
||||
const char* Bcc() const;
|
||||
void SetBcc(const char* bcc);
|
||||
|
||||
bool IsSubjectEmpty() const;
|
||||
const char* Subject() const;
|
||||
void SetSubject(const char* subject);
|
||||
|
||||
class TTextControl : public BComboBox {
|
||||
public:
|
||||
TTextControl(BRect, const char*, BMessage*, bool,
|
||||
bool, int32 resizingMode = B_FOLLOW_NONE);
|
||||
bool IsDateEmpty() const;
|
||||
const char* Date() const;
|
||||
void SetDate(const char* date);
|
||||
|
||||
virtual void AttachedToWindow();
|
||||
virtual void MessageReceived(BMessage*);
|
||||
int32 AccountID() const;
|
||||
const char* AccountName() const;
|
||||
void SetAccount(int32 id);
|
||||
void SetAccount(const char* name);
|
||||
|
||||
bool HasFocus();
|
||||
status_t SetFromMessage(BEmailMessage* mail);
|
||||
|
||||
virtual void MessageReceived(BMessage*);
|
||||
virtual void AttachedToWindow();
|
||||
|
||||
private:
|
||||
bool fIncoming;
|
||||
bool fResending;
|
||||
char fLabel[100];
|
||||
BPopUpMenu* fRefDropMenu;
|
||||
int32 fCommand;
|
||||
BPopUpMenu* fAccountMenu;
|
||||
int32 fAccountID;
|
||||
BTextControl* fAccount;
|
||||
BTextControl* fFromControl;
|
||||
LabelView* fToLabel;
|
||||
AddressTextControl* fToControl;
|
||||
LabelView* fCcLabel;
|
||||
AddressTextControl* fCcControl;
|
||||
AddressTextControl* fBccControl;
|
||||
BTextControl* fSubjectControl;
|
||||
BStringView* fDateView;
|
||||
bool fIncoming;
|
||||
bool fResending;
|
||||
};
|
||||
|
||||
#endif /* _HEADER_H */
|
||||
|
||||
#endif /* _HEADER_H */
|
||||
|
||||
+11
-1
@@ -10,10 +10,12 @@ UsePrivateHeaders textencoding ;
|
||||
UsePrivateHeaders shared ;
|
||||
UsePrivateHeaders storage ;
|
||||
|
||||
SEARCH_SOURCE += [ FDirName $(HAIKU_TOP) src apps webpositive autocompletion ] ;
|
||||
|
||||
AddResources Mail : pictures.rdef ;
|
||||
|
||||
Application Mail :
|
||||
ComboBox.cpp
|
||||
AddressTextControl.cpp
|
||||
Content.cpp
|
||||
Enclosures.cpp
|
||||
FindWindow.cpp
|
||||
@@ -23,7 +25,9 @@ Application Mail :
|
||||
MailSupport.cpp
|
||||
MailWindow.cpp
|
||||
MessageStatus.cpp
|
||||
People.cpp
|
||||
Prefs.cpp
|
||||
QueryList.cpp
|
||||
QueryMenu.cpp
|
||||
Signature.cpp
|
||||
Status.cpp
|
||||
@@ -31,6 +35,12 @@ Application Mail :
|
||||
WIndex.cpp
|
||||
Words.cpp
|
||||
KUndoBuffer.cpp
|
||||
|
||||
# autocompletion
|
||||
AutoCompleter.cpp
|
||||
AutoCompleterDefaultImpl.cpp
|
||||
TextViewCompleter.cpp
|
||||
|
||||
: libshared.a be tracker [ TargetLibstdc++ ] [ TargetLibsupc++ ]
|
||||
localestub libmail.so libtextencoding.so
|
||||
: Mail.rdef
|
||||
|
||||
@@ -116,7 +116,10 @@ TMailApp::TMailApp()
|
||||
fStartWithSpellCheckOn(false),
|
||||
fShowSpamGUI(true),
|
||||
fMailCharacterSet(B_MAIL_UTF8_CONVERSION),
|
||||
fContentFont(be_fixed_font)
|
||||
fContentFont(be_fixed_font),
|
||||
|
||||
fPeople(fPeopleQueryList),
|
||||
fPeopleGroups(fPeopleQueryList)
|
||||
{
|
||||
// set default values
|
||||
fContentFont.SetSize(12.0);
|
||||
@@ -461,6 +464,9 @@ TMailApp::ReadyToRun()
|
||||
fs_create_index(volume.Device(), INDEX_STATUS, B_STRING_TYPE, 0);
|
||||
fs_create_index(volume.Device(), B_MAIL_ATTR_FLAGS, B_INT32_TYPE, 0);
|
||||
|
||||
// Start people queries
|
||||
fPeopleQueryList.Init("META:email=**");
|
||||
|
||||
// Load dictionaries
|
||||
BPath indexDir;
|
||||
BPath dictionaryDir;
|
||||
|
||||
@@ -34,6 +34,7 @@ All rights reserved.
|
||||
#ifndef _MAIL_APP_H
|
||||
#define _MAIL_APP_H
|
||||
|
||||
|
||||
#include <Application.h>
|
||||
#include <Catalog.h>
|
||||
#include <Entry.h>
|
||||
@@ -41,6 +42,9 @@ All rights reserved.
|
||||
#include <List.h>
|
||||
#include <String.h>
|
||||
|
||||
#include "People.h"
|
||||
#include "QueryList.h"
|
||||
|
||||
|
||||
class BFile;
|
||||
class BMessenger;
|
||||
@@ -90,6 +94,13 @@ class TMailApp : public BApplication {
|
||||
{ return fShowSpamGUI; }
|
||||
BFont ContentFont();
|
||||
|
||||
QueryList& PeopleQueryList()
|
||||
{ return fPeopleQueryList; }
|
||||
PersonList& People()
|
||||
{ return fPeople; }
|
||||
GroupList& PeopleGroups()
|
||||
{ return fPeopleGroups; }
|
||||
|
||||
private:
|
||||
void _ClearPrintSettings();
|
||||
void _CheckForSpamFilterExistence();
|
||||
@@ -128,6 +139,10 @@ class TMailApp : public BApplication {
|
||||
int32 fUseAccountFrom;
|
||||
uint32 fMailCharacterSet;
|
||||
BFont fContentFont;
|
||||
|
||||
QueryList fPeopleQueryList;
|
||||
PersonList fPeople;
|
||||
GroupList fPeopleGroups;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -166,4 +166,3 @@ add_query_menu_items(BMenu* menu, const char* attribute, uint32 what,
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
|
||||
+272
-296
File diff suppressed because it is too large
Load Diff
@@ -124,8 +124,6 @@ protected:
|
||||
status_t TrainMessageAs(const char* commandWord);
|
||||
|
||||
private:
|
||||
void _UpdateSizeLimits();
|
||||
|
||||
status_t _GetQueryPath(BPath* path) const;
|
||||
void _RebuildQueryMenu(bool firstTime = false);
|
||||
char* _BuildQueryString(BEntry* entry) const;
|
||||
@@ -134,6 +132,7 @@ private:
|
||||
void _UpdateReadButton();
|
||||
|
||||
void _SetDownloading(bool downloading);
|
||||
uint32 _CurrentCharacterSet() const;
|
||||
|
||||
static BBitmap* _RetrieveVectorIcon(int32 id);
|
||||
|
||||
@@ -169,6 +168,7 @@ private:
|
||||
|
||||
BMenu* fQueryMenu;
|
||||
BMenu* fLeaveStatusMenu;
|
||||
BMenu* fEncodingMenu;
|
||||
|
||||
struct BitmapItem {
|
||||
BBitmap* bm;
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
/*
|
||||
* Copyright 2015, Axel Dörfler, axeld@pinc-software.de.
|
||||
* Distributed under the terms of the MIT License.
|
||||
*/
|
||||
|
||||
|
||||
#include "People.h"
|
||||
|
||||
#include <Autolock.h>
|
||||
#include <Node.h>
|
||||
|
||||
|
||||
static BString
|
||||
PersonName(BNode& node)
|
||||
{
|
||||
BString fullName;
|
||||
node.ReadAttrString("META:name", &fullName);
|
||||
|
||||
return fullName;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
AddPersonAddresses(BNode& node, BStringList& addresses)
|
||||
{
|
||||
BString email;
|
||||
if (node.ReadAttrString("META:email", &email) != B_OK || email.IsEmpty())
|
||||
return;
|
||||
|
||||
addresses.Add(email);
|
||||
|
||||
// Support for 3rd-party People apps
|
||||
for (int i = 2; i < 99; i++) {
|
||||
char attr[32];
|
||||
snprintf(attr, sizeof(attr), "META:email%d", i);
|
||||
|
||||
if (node.ReadAttrString(attr, &email) != B_OK)
|
||||
break;
|
||||
|
||||
addresses.Add(email);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
AddPersonGroups(BNode& node, BStringList& groups)
|
||||
{
|
||||
BString groupString;
|
||||
if (node.ReadAttrString("META:group", &groupString) != B_OK
|
||||
|| groupString.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int first = 0;
|
||||
while (first < groupString.Length()) {
|
||||
int end = groupString.FindFirst(',', first);
|
||||
if (end < 0)
|
||||
end = groupString.Length();
|
||||
|
||||
BString group;
|
||||
groupString.CopyInto(group, first, end - first);
|
||||
group.Trim();
|
||||
groups.Add(group);
|
||||
|
||||
first = end + 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// #pragma mark - Person
|
||||
|
||||
|
||||
Person::Person(const entry_ref& ref)
|
||||
{
|
||||
BNode node(&ref);
|
||||
if (node.InitCheck() != B_OK)
|
||||
return;
|
||||
|
||||
fName = PersonName(node);
|
||||
AddPersonAddresses(node, fAddresses);
|
||||
AddPersonGroups(node, fGroups);
|
||||
}
|
||||
|
||||
|
||||
Person::~Person()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
Person::IsInGroup(const char* group) const
|
||||
{
|
||||
for (int32 index = 0; index < CountGroups(); index++) {
|
||||
if (GroupAt(index) == group)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// #pragma mark - PersonList
|
||||
|
||||
|
||||
PersonList::PersonList(QueryList& query)
|
||||
:
|
||||
fQueryList(query),
|
||||
fPersons(10, true)
|
||||
{
|
||||
fQueryList.AddListener(this);
|
||||
}
|
||||
|
||||
|
||||
PersonList::~PersonList()
|
||||
{
|
||||
fQueryList.RemoveListener(this);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
PersonList::EntryCreated(QueryList& source, const entry_ref& ref, ino_t node)
|
||||
{
|
||||
BAutolock locker(this);
|
||||
|
||||
Person* person = new Person(ref);
|
||||
fPersons.AddItem(person);
|
||||
fPersonMap.insert(std::make_pair(node_ref(ref.device, node), person));
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
PersonList::EntryRemoved(QueryList& source, const node_ref& nodeRef)
|
||||
{
|
||||
BAutolock locker(this);
|
||||
|
||||
PersonMap::iterator found = fPersonMap.find(nodeRef);
|
||||
if (found != fPersonMap.end()) {
|
||||
Person* person = found->second;
|
||||
fPersons.RemoveItem(person);
|
||||
fPersonMap.erase(found);
|
||||
delete person;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// #pragma mark - GroupList
|
||||
|
||||
|
||||
GroupList::GroupList(QueryList& query)
|
||||
:
|
||||
fQueryList(query)
|
||||
{
|
||||
fQueryList.AddListener(this);
|
||||
}
|
||||
|
||||
|
||||
GroupList::~GroupList()
|
||||
{
|
||||
fQueryList.RemoveListener(this);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
GroupList::EntryCreated(QueryList& source, const entry_ref& ref, ino_t _node)
|
||||
{
|
||||
BNode node(&ref);
|
||||
if (node.InitCheck() != B_OK)
|
||||
return;
|
||||
|
||||
BAutolock locker(this);
|
||||
|
||||
BStringList groups;
|
||||
AddPersonGroups(node, groups);
|
||||
|
||||
for (int32 index = 0; index < groups.CountStrings(); index++) {
|
||||
BString group = groups.StringAt(index);
|
||||
|
||||
StringCountMap::iterator found = fGroupMap.find(group);
|
||||
if (found != fGroupMap.end())
|
||||
found->second++;
|
||||
else {
|
||||
fGroupMap[group] = 1;
|
||||
fGroups.Add(group);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: sort groups
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
GroupList::EntryRemoved(QueryList& source, const node_ref& nodeRef)
|
||||
{
|
||||
// TODO!
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2015, Axel Dörfler, axeld@pinc-software.de.
|
||||
* Distributed under the terms of the MIT License.
|
||||
*/
|
||||
#ifndef PEOPLE_H
|
||||
#define PEOPLE_H
|
||||
|
||||
|
||||
#include <map>
|
||||
|
||||
#include <Locker.h>
|
||||
#include <ObjectList.h>
|
||||
#include <String.h>
|
||||
#include <StringList.h>
|
||||
|
||||
#include "QueryList.h"
|
||||
|
||||
|
||||
class Person {
|
||||
public:
|
||||
Person(const entry_ref& ref);
|
||||
virtual ~Person();
|
||||
|
||||
const BString& Name() const
|
||||
{ return fName; }
|
||||
|
||||
int32 CountAddresses() const
|
||||
{ return fAddresses.CountStrings(); }
|
||||
BString AddressAt(int32 index) const
|
||||
{ return fAddresses.StringAt(index); }
|
||||
|
||||
int32 CountGroups() const
|
||||
{ return fGroups.CountStrings(); }
|
||||
BString GroupAt(int32 index) const
|
||||
{ return fGroups.StringAt(index); }
|
||||
bool IsInGroup(const char* group) const;
|
||||
|
||||
private:
|
||||
BString fName;
|
||||
BStringList fAddresses;
|
||||
BStringList fGroups;
|
||||
};
|
||||
|
||||
|
||||
class PersonList : public QueryListener, public BLocker {
|
||||
public:
|
||||
PersonList(QueryList& query);
|
||||
~PersonList();
|
||||
|
||||
int32 CountPersons() const
|
||||
{ return fPersons.CountItems(); }
|
||||
const Person* PersonAt(int32 index) const
|
||||
{ return fPersons.ItemAt(index); }
|
||||
|
||||
virtual void EntryCreated(QueryList& source,
|
||||
const entry_ref& ref, ino_t node);
|
||||
virtual void EntryRemoved(QueryList& source,
|
||||
const node_ref& nodeRef);
|
||||
|
||||
private:
|
||||
typedef std::map<node_ref, Person*> PersonMap;
|
||||
|
||||
QueryList& fQueryList;
|
||||
BObjectList<Person> fPersons;
|
||||
PersonMap fPersonMap;
|
||||
};
|
||||
|
||||
|
||||
class GroupList : public QueryListener, public BLocker {
|
||||
public:
|
||||
GroupList(QueryList& query);
|
||||
~GroupList();
|
||||
|
||||
int32 CountGroups() const
|
||||
{ return fGroups.CountStrings(); }
|
||||
BString GroupAt(int32 index) const
|
||||
{ return fGroups.StringAt(index); }
|
||||
|
||||
virtual void EntryCreated(QueryList& source,
|
||||
const entry_ref& ref, ino_t node);
|
||||
virtual void EntryRemoved(QueryList& source,
|
||||
const node_ref& nodeRef);
|
||||
|
||||
private:
|
||||
typedef std::map<BString, int> StringCountMap;
|
||||
|
||||
QueryList& fQueryList;
|
||||
BStringList fGroups;
|
||||
StringCountMap fGroupMap;
|
||||
};
|
||||
|
||||
|
||||
#endif // ADDRESS_TEXT_CONTROL_H
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* Copyright 2015, Axel Dörfler, axeld@pinc-software.de.
|
||||
* Distributed under the terms of the MIT License.
|
||||
*/
|
||||
|
||||
|
||||
#include "QueryList.h"
|
||||
|
||||
#include <Autolock.h>
|
||||
#include <Debug.h>
|
||||
#include <NodeMonitor.h>
|
||||
#include <VolumeRoster.h>
|
||||
|
||||
|
||||
static BLooper* sQueryLooper = NULL;
|
||||
static pthread_once_t sInitOnce = PTHREAD_ONCE_INIT;
|
||||
|
||||
|
||||
static void
|
||||
initQueryLooper()
|
||||
{
|
||||
sQueryLooper = new BLooper("query looper");
|
||||
sQueryLooper->Run();
|
||||
}
|
||||
|
||||
|
||||
// #pragma mark -
|
||||
|
||||
|
||||
QueryList::QueryList()
|
||||
:
|
||||
fQuit(false),
|
||||
fListeners(5, true)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
QueryList::~QueryList()
|
||||
{
|
||||
fQuit = true;
|
||||
|
||||
ThreadVector::const_iterator threadIterator = fFetchThreads.begin();
|
||||
for (; threadIterator != fFetchThreads.end(); threadIterator++) {
|
||||
wait_for_thread(*threadIterator, NULL);
|
||||
}
|
||||
|
||||
QueryVector::iterator queryIterator = fQueries.begin();
|
||||
for (; queryIterator != fQueries.end(); queryIterator++) {
|
||||
delete *queryIterator;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
QueryList::Init(const char* predicate, BVolume* specificVolume)
|
||||
{
|
||||
if (sQueryLooper == NULL)
|
||||
pthread_once(&sInitOnce, &initQueryLooper);
|
||||
|
||||
if (Looper() != NULL)
|
||||
debugger("Init() called twice!");
|
||||
|
||||
sQueryLooper->Lock();
|
||||
sQueryLooper->AddHandler(this);
|
||||
sQueryLooper->Unlock();
|
||||
|
||||
if (specificVolume == NULL) {
|
||||
BVolumeRoster roster;
|
||||
BVolume volume;
|
||||
|
||||
while (roster.GetNextVolume(&volume) == B_OK) {
|
||||
if (volume.KnowsQuery() && volume.KnowsAttr() && volume.KnowsMime())
|
||||
_AddVolume(volume, predicate);
|
||||
}
|
||||
} else
|
||||
_AddVolume(*specificVolume, predicate);
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
QueryList::AddListener(QueryListener* listener)
|
||||
{
|
||||
BAutolock locker(this);
|
||||
|
||||
// Add all entries that were already retrieved
|
||||
RefMap::const_iterator iterator = fRefs.begin();
|
||||
for (; iterator != fRefs.end(); iterator++) {
|
||||
listener->EntryCreated(*this, iterator->second, iterator->first.node);
|
||||
}
|
||||
|
||||
fListeners.AddItem(listener);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
QueryList::RemoveListener(QueryListener* listener)
|
||||
{
|
||||
BAutolock locker(this);
|
||||
fListeners.RemoveItem(listener, false);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
QueryList::MessageReceived(BMessage* message)
|
||||
{
|
||||
switch (message->what) {
|
||||
case B_QUERY_UPDATE:
|
||||
{
|
||||
int32 opcode = message->GetInt32("opcode", -1);
|
||||
int64 directory = message->GetInt64("directory", -1);
|
||||
int32 device = message->GetInt32("device", -1);
|
||||
int64 node = message->GetInt64("node", -1);
|
||||
|
||||
if (opcode == B_ENTRY_CREATED) {
|
||||
const char* name = message->GetString("name");
|
||||
if (name != NULL) {
|
||||
entry_ref ref(device, directory, name);
|
||||
_AddEntry(ref, node);
|
||||
}
|
||||
} else if (opcode == B_ENTRY_REMOVED) {
|
||||
node_ref nodeRef(device, node);
|
||||
_RemoveEntry(nodeRef);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
BHandler::MessageReceived(message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
QueryList::_AddEntry(const entry_ref& ref, ino_t node)
|
||||
{
|
||||
BAutolock locker(this);
|
||||
|
||||
// TODO: catch bad_alloc
|
||||
fRefs.insert(std::make_pair(node_ref(ref.device, node), ref));
|
||||
|
||||
_NotifyEntryCreated(ref, node);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
QueryList::_RemoveEntry(const node_ref& nodeRef)
|
||||
{
|
||||
BAutolock locker(this);
|
||||
RefMap::iterator found = fRefs.find(nodeRef);
|
||||
if (found != fRefs.end())
|
||||
_NotifyEntryRemoved(nodeRef);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
QueryList::_NotifyEntryCreated(const entry_ref& ref, ino_t node)
|
||||
{
|
||||
ASSERT(IsLocked());
|
||||
|
||||
int32 count = fListeners.CountItems();
|
||||
for (int32 index = 0; index < count; index++) {
|
||||
fListeners.ItemAt(index)->EntryCreated(*this, ref, node);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
QueryList::_NotifyEntryRemoved(const node_ref& nodeRef)
|
||||
{
|
||||
ASSERT(IsLocked());
|
||||
|
||||
int32 count = fListeners.CountItems();
|
||||
for (int32 index = 0; index < count; index++) {
|
||||
fListeners.ItemAt(index)->EntryRemoved(*this, nodeRef);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
QueryList::_AddVolume(BVolume& volume, const char* predicate)
|
||||
{
|
||||
BQuery* query = new BQuery();
|
||||
if (query->SetVolume(&volume) != B_OK
|
||||
|| query->SetPredicate(predicate) != B_OK
|
||||
|| query->SetTarget(this) != B_OK) {
|
||||
delete query;
|
||||
}
|
||||
|
||||
// TODO: catch bad_alloc
|
||||
fQueries.push_back(query);
|
||||
Lock();
|
||||
fQueryQueue.push_back(query);
|
||||
Unlock();
|
||||
|
||||
thread_id thread = spawn_thread(_FetchQuery, "query fetcher",
|
||||
B_NORMAL_PRIORITY, this);
|
||||
if (thread >= B_OK) {
|
||||
resume_thread(thread);
|
||||
|
||||
fFetchThreads.push_back(thread);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*static*/ status_t
|
||||
QueryList::_FetchQuery(void* self)
|
||||
{
|
||||
return static_cast<QueryList*>(self)->_FetchQuery();
|
||||
}
|
||||
|
||||
|
||||
status_t
|
||||
QueryList::_FetchQuery()
|
||||
{
|
||||
RefMap map;
|
||||
|
||||
BAutolock locker(this);
|
||||
BQuery* query = fQueryQueue.back();
|
||||
fQueryQueue.pop_back();
|
||||
locker.Unlock();
|
||||
|
||||
query->Fetch();
|
||||
|
||||
entry_ref ref;
|
||||
while (!fQuit && query->GetNextRef(&ref) == B_OK) {
|
||||
BEntry entry(&ref);
|
||||
node_ref nodeRef;
|
||||
if (entry.GetNodeRef(&nodeRef) == B_OK)
|
||||
map.insert(std::make_pair(nodeRef, ref));
|
||||
}
|
||||
if (fQuit)
|
||||
return B_INTERRUPTED;
|
||||
|
||||
locker.Lock();
|
||||
|
||||
RefMap::const_iterator iterator = map.begin();
|
||||
for (; iterator != map.end(); iterator++) {
|
||||
_AddEntry(iterator->second, iterator->first.node);
|
||||
}
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright 2015, Axel Dörfler, axeld@pinc-software.de.
|
||||
* Distributed under the terms of the MIT License.
|
||||
*/
|
||||
#ifndef QUERY_LIST_H
|
||||
#define QUERY_LIST_H
|
||||
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include <Entry.h>
|
||||
#include <Handler.h>
|
||||
#include <Locker.h>
|
||||
#include <ObjectList.h>
|
||||
#include <Query.h>
|
||||
|
||||
|
||||
struct QueryList;
|
||||
|
||||
|
||||
class QueryListener {
|
||||
public:
|
||||
virtual void EntryCreated(QueryList& source,
|
||||
const entry_ref& ref, ino_t node) = 0;
|
||||
virtual void EntryRemoved(QueryList& source,
|
||||
const node_ref& nodeRef) = 0;
|
||||
};
|
||||
|
||||
|
||||
typedef std::map<node_ref, entry_ref> RefMap;
|
||||
|
||||
|
||||
class QueryList : public BHandler, public BLocker {
|
||||
public:
|
||||
QueryList();
|
||||
virtual ~QueryList();
|
||||
|
||||
status_t Init(const char* predicate,
|
||||
BVolume* volume = NULL);
|
||||
|
||||
void AddListener(QueryListener* listener);
|
||||
void RemoveListener(QueryListener* listener);
|
||||
|
||||
const RefMap& Entries() const
|
||||
{ return fRefs; }
|
||||
|
||||
virtual void MessageReceived(BMessage* message);
|
||||
|
||||
private:
|
||||
void _AddEntry(const entry_ref& ref, ino_t node);
|
||||
void _RemoveEntry(const node_ref& nodeRef);
|
||||
void _NotifyEntryCreated(const entry_ref& ref,
|
||||
ino_t node);
|
||||
void _NotifyEntryRemoved(const node_ref& nodeRef);
|
||||
void _AddVolume(BVolume& volume,
|
||||
const char* predicate);
|
||||
|
||||
static status_t _FetchQuery(void* self);
|
||||
status_t _FetchQuery();
|
||||
|
||||
private:
|
||||
typedef std::vector<thread_id> ThreadVector;
|
||||
typedef std::vector<BQuery*> QueryVector;
|
||||
|
||||
bool fQuit;
|
||||
RefMap fRefs;
|
||||
QueryVector fQueries;
|
||||
QueryVector fQueryQueue;
|
||||
ThreadVector fFetchThreads;
|
||||
BObjectList<QueryListener> fListeners;
|
||||
};
|
||||
|
||||
|
||||
#endif // QUERY_LIST_H
|
||||
Reference in New Issue
Block a user