diff --git a/src/apps/mail/AddressTextControl.cpp b/src/apps/mail/AddressTextControl.cpp new file mode 100644 index 0000000000..8fec66596d --- /dev/null +++ b/src/apps/mail/AddressTextControl.cpp @@ -0,0 +1,987 @@ +/* + * Copyright 2015, Axel Dörfler, axeld@pinc-software.de. + * Copyright 2010 Stephan Aßmus + * Distributed under the terms of the MIT License. + */ + + +#include "AddressTextControl.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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(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 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(be_app)->PeopleQueryList().AddListener(this); +} + + +AddressPopUpMenu::~AddressPopUpMenu() +{ + static_cast(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(be_app)->People(); + BAutolock locker(peopleList); + + if (peopleList.CountPersons() > 0) + _AddGroup(B_TRANSLATE("All people"), NULL, peopleList); + + GroupList& groupList = static_cast(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); +} diff --git a/src/apps/mail/AddressTextControl.h b/src/apps/mail/AddressTextControl.h new file mode 100644 index 0000000000..0ba6074b85 --- /dev/null +++ b/src/apps/mail/AddressTextControl.h @@ -0,0 +1,62 @@ +/* + * Copyright 2015, Axel Dörfler, axeld@pinc-software.de. + * Distributed under the terms of the MIT License. + */ +#ifndef ADDRESS_TEXT_CONTROL_H +#define ADDRESS_TEXT_CONTROL_H + + +#include + + +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 + diff --git a/src/apps/mail/ComboBox.cpp b/src/apps/mail/ComboBox.cpp deleted file mode 100644 index 0424ac066c..0000000000 --- a/src/apps/mail/ComboBox.cpp +++ /dev/null @@ -1,1984 +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.cpp -// -// - -/* - TODO: - - Better up/down arrow handling (if text in input box matches a list item, - pressing down should select the next item, pressing up should select the - previous. If no item matched, the first or last item should be selected. - In any case, pressing up or down should show the popup if it is hidden - - Properly draw the label, taking alignment into account - - Draw nicer border around text input and popup window - - Escaping out of the popup menu should restore the text in the input to the - value it had previous to popping up the menu. - - Fix popup behavior when the widget is near the bottom of the screen. The - popup window should be able to go above the text input area. Also, the popup - should size itself in a smart manner so that it is small if there are few - choices and large if there are many and the window under it is big. Perhaps - the developer should be able to influence the size of the popup. - - Improve button drawing and (?) button behavior - - Fix and test enable/disable behavior - - Add auto-scrolling and/or drag-scrolling to the poup-menu - - Add support for other navigation keys, like page up, page down, home, end. - - Fix up choice functions (remove choice, add at index, etc) and make sure they - properly invalidate/scroll/etc the list when it is visible - - Change auto-complete behavior to be non-greedy, or perhaps add some type of - tab-cycling to the choices - - Add mode whereby you can pop up a list of only those items that match -*/ - -#include -#include -#include -#include -#include -#include // for menu_info -#include -#include "ObjectList.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include "ComboBox.h" - -//static const uint32 kTextControlInvokeMessage = 'tCIM'; -static const uint32 kTextInputModifyMessage = 'tIMM'; -static const uint32 kPopupButtonInvokeMessage = 'pBIM'; -static const uint32 kPopupWindowHideMessage = 'pUWH'; -static const uint32 kWindowMovedMessage = 'wMOV'; - -static const float kTextInputMargin = (float)3.0; -static const float kLabelRightMargin = (float)6.0; -static const float kButtonWidth = (float)15.0; - -#define disable_color(_c_) tint_color(_c_, B_DISABLED_LABEL_TINT) - -#define TV_MARGIN 3.0 -#define TV_DIVIDER_MARGIN 6.0 - -rgb_color create_color(uchar r, uchar g, uchar b, uchar a = 255); - -rgb_color create_color(uchar r, uchar g, uchar b, uchar a) { - rgb_color col; - col.red = r; - col.green = g; - col.blue = b; - col.alpha = a; - return col; -} - -class StringObjectList : public BObjectList {}; - -// ---------------------------------------------------------------------------- - -// ChoiceListView is similar to a BListView, but it's implementation is tied to -// BComboBox. BListView is not used because it requires that a BStringItem be -// created for each choice. ChoiceListView just pulls the choice strings -// directly from the BComboBox and draws them. -class BComboBox::ChoiceListView : public BView -{ - public: - ChoiceListView( BRect frame, BComboBox *parent); - virtual ~ChoiceListView(); - - virtual void Draw(BRect update); - virtual void MouseDown(BPoint where); - virtual void MouseUp(BPoint where); - virtual void MouseMoved(BPoint where, uint32 transit, - const BMessage *dragMessage); - virtual void KeyDown(const char *bytes, int32 numBytes); - virtual void SetFont(const BFont *font, uint32 properties = B_FONT_ALL); - - void ScrollToSelection(); - void InvalidateItem(int32 index, bool force = false); - BRect ItemFrame(int32 index); - void AdjustScrollBar(); - // XXX: add BArchivable functionality - - private: - inline float LineHeight(); - - BPoint fClickLoc; - font_height fFontHeight; - bigtime_t fClickTime; - rgb_color fForeCol; - rgb_color fBackCol; - rgb_color fSelCol; - int32 fSelIndex; - BComboBox *fParent; - bool fTrackingMouseDown; -}; - - -// ---------------------------------------------------------------------------- - -// TextInput is a somewhat modified version of the _BTextInput_ class defined -// in TextControl.cpp. - -class BComboBox::TextInput : public BTextView { - public: - TextInput(BRect rect, BRect trect, ulong rMask, ulong flags); - TextInput(BMessage *data); - virtual ~TextInput(); - static BArchivable *Instantiate(BMessage *data); - virtual status_t Archive(BMessage *data, bool deep = true) const; - - virtual void KeyDown(const char *bytes, int32 numBytes); - virtual void MakeFocus(bool state); - virtual void FrameResized(float x, float y); - virtual void Paste(BClipboard *clipboard); - - void AlignTextRect(); - - void SetInitialText(); - void SetFilter(text_input_filter_hook hook); - - // XXX: add BArchivable functionality - - protected: - virtual void InsertText(const char *inText, int32 inLength, int32 inOffset, - const text_run_array *inRuns); - virtual void DeleteText(int32 fromOffset, int32 toOffset); - - private: - char *fInitialText; - text_input_filter_hook fFilter; - bool fClean; -}; - -// ---------------------------------------------------------------------------- - -class BComboBox::ComboBoxWindow : public BWindow -{ - public: - ComboBoxWindow(BComboBox *box); - virtual ~ComboBoxWindow(); - virtual void WindowActivated(bool active); - virtual void FrameResized(float width, float height); - - void DoPosition(); - BComboBox::ChoiceListView *ListView(); - BScrollBar *ScrollBar(); - - // XXX: add BArchivable functionality - - private: - BScrollBar *fScrollBar; - ChoiceListView *fListView; - BComboBox *fParent; -}; - -// ---------------------------------------------------------------------------- - -// In BeOS R4.5, SetEventMask(B_POINTER_EVENTS, ...) does not work for getting -// all mouse events as they happen. Specifically, when the user clicks on the -// window dressing (the borders or the title tab) no mouse event will be -// delivered until after the user releases the mouse button. This has the -// unfortunate side effect of allowing the user to move the window that -// contains the BComboBox around with no notification being sent to the -// BComboBox. We need to intercept the B_WINDOW_MOVED messages so that we can -// hide the popup window when the window moves. - -class BComboBox::MovedMessageFilter : public BMessageFilter -{ - public: - MovedMessageFilter(BHandler *target); - virtual filter_result Filter(BMessage *message, BHandler **target); - - private: - BHandler *fTarget; -}; - - -// ---------------------------------------------------------------------------- - - -BComboBox::ChoiceListView::ChoiceListView(BRect frame, BComboBox *parent) - : BView(frame, "_choice_list_view_", B_FOLLOW_ALL_SIDES, B_WILL_DRAW - | B_NAVIGABLE), - fClickLoc(-100, -100) -{ - fParent = parent; - GetFontHeight(&fFontHeight); - menu_info mi; - get_menu_info(&mi); - fForeCol = create_color(0, 0, 0); - fBackCol = mi.background_color; - fSelCol = create_color(144, 144, 144); - SetViewColor(B_TRANSPARENT_COLOR); - SetHighColor(fForeCol); - fTrackingMouseDown = false; - fClickTime = 0; -} - - -BComboBox::ChoiceListView::~ChoiceListView() -{ -} - - -void BComboBox::ChoiceListView::Draw(BRect update) -{ - float h = LineHeight(); - BRect rect(Bounds()); - int32 index; - int32 choices = fParent->fChoiceList->CountChoices(); - int32 selected = (fTrackingMouseDown) ? fSelIndex : fParent->CurrentSelection(); - - // draw each visible item - for (index = (int32)floor(update.top / h); index < choices; index++) - { - rect.top = index * h; - rect.bottom = rect.top + h; - SetLowColor((index == selected) ? fSelCol : fBackCol); - FillRect(rect, B_SOLID_LOW); - DrawString(fParent->fChoiceList->ChoiceAt(index), BPoint(rect.left + 2, - rect.bottom - fFontHeight.descent - 1)); - } - - // draw empty area on bottom - if (rect.bottom < update.bottom) - { - update.top = rect.bottom; - SetLowColor(fBackCol); - FillRect(update, B_SOLID_LOW); - } -} - - -void BComboBox::ChoiceListView::MouseDown(BPoint where) -{ - BRect rect(Window()->Frame()); - ConvertFromScreen(&rect); - if (!rect.Contains(where)) - { - // hide the popup window when the user clicks outside of it - if (fParent->Window()->Lock()) - { - fParent->HidePopupWindow(); - fParent->Window()->Unlock(); - } - - // HACK: the window is locked and unlocked so that it will get - // activated before we potentially send the mouse down event in the - // code below. Is there a way to wait until the window is activated - // before sending the mouse down? Should we call - // fParent->Window()->MakeActive(true) here? - - if (fParent->Window()->Lock()) - { - // resend the mouse event to the textinput, if necessary - BTextView *text = fParent->TextView(); - BPoint screenWhere(ConvertToScreen(where)); - rect = text->Window()->ConvertToScreen(text->Frame()); - if (rect.Contains(screenWhere)) - { - //printf(" resending mouse down to textinput\n"); - BMessage *msg = new BMessage(*Window()->CurrentMessage()); - msg->RemoveName("be:view_where"); - text->ConvertFromScreen(&screenWhere); - msg->AddPoint("be:view_where", screenWhere); - text->Window()->PostMessage(msg, text); - delete msg; - } - fParent->Window()->Unlock(); - } - - return; - } - - rect = Bounds(); - if (!rect.Contains(where)) - return; - - fTrackingMouseDown = true; - // check for double click - bigtime_t now = system_time(); - bigtime_t clickSpeed; - get_click_speed(&clickSpeed); - if ((now - fClickTime < clickSpeed) - && ((abs((int)(fClickLoc.x - where.x)) < 3) - && (abs((int)(fClickLoc.y - where.y)) < 3))) - { - // this is a double click - // XXX: what to do here? - printf("BComboBox::ChoiceListView::MouseDown() -- unhandled double click\n"); - } - fClickTime = now; - fClickLoc = where; - - float h = LineHeight(); - int32 oldIndex = fSelIndex; - fSelIndex = (int32)floor(where.y / h); - int32 choices = fParent->fChoiceList->CountChoices(); - if (fSelIndex < 0 || fSelIndex >= choices) - fSelIndex = -1; - - if (oldIndex != fSelIndex) - { - InvalidateItem(oldIndex); - InvalidateItem(fSelIndex); - } - // XXX: this probably isn't necessary since we are doing a SetEventMask - // whenever the popup window becomes visible which routes all mouse events - // to this view -// SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS); -} - - -void BComboBox::ChoiceListView::MouseUp(BPoint /*where*/) -{ - if (fTrackingMouseDown) - { - fTrackingMouseDown = false; - if (fSelIndex >= 0) - fParent->Select(fSelIndex, true); - else - fParent->Deselect(); - } -// fClickLoc = where; -} - - -void BComboBox::ChoiceListView::MouseMoved(BPoint where, uint32 /*transit*/, - const BMessage */*dragMessage*/) -{ - if (fTrackingMouseDown) - { - float h = LineHeight(); - int32 oldIndex = fSelIndex; - fSelIndex = (int32)floor(where.y / h); - int32 choices = fParent->fChoiceList->CountChoices(); - if (fSelIndex < 0 || fSelIndex >= choices) - fSelIndex = -1; - - if (oldIndex != fSelIndex) - { - InvalidateItem(oldIndex); - InvalidateItem(fSelIndex); - } - } -} - - -void BComboBox::ChoiceListView::KeyDown(const char *bytes, int32 /*numBytes*/) -{ - BComboBox *cb = fParent; - BWindow *win = cb->Window(); - BComboBox::TextInput *text = dynamic_cast(cb->TextView()); - uchar aKey = bytes[0]; - - switch (aKey) - { - case B_UP_ARROW: // fall through - case B_DOWN_ARROW: - if (win->Lock()) - { - // change the selection - int32 index = cb->CurrentSelection(); - int32 choices = cb->fChoiceList->CountChoices(); - if (choices > 0) - { - if (index < 0) - { - // no previous selection, so select first or last item - // depending on whether this is a up or down arrow - cb->Select((aKey == B_UP_ARROW) ? choices - 1 : 0); - } - else - { - // select the previous or the next item, if possible, - // depending on whether this is an up or down arrow - if (aKey == B_UP_ARROW && (index - 1 >= 0)) - cb->Select(index - 1, true); - else if (aKey == B_DOWN_ARROW && (index + 1 < choices)) - cb->Select(index + 1, true); - } - } - win->Unlock(); - } - break; - default: - { // send all other key down events to the text input view - BMessage *msg = Window()->DetachCurrentMessage(); - if (msg) { - win->PostMessage(msg, text); - delete msg; - } - break; - } - } -} - - -void BComboBox::ChoiceListView::SetFont(const BFont *font, uint32 properties) -{ - BView::SetFont(font, properties); - GetFontHeight(&fFontHeight); - Invalidate(); -} - - -void BComboBox::ChoiceListView::ScrollToSelection() -{ - int32 selected = fParent->CurrentSelection(); - if (selected >= 0) - { - BRect frame(ItemFrame(selected)); - BRect bounds(Bounds()); - float newY = -1.0; // dummy value -- not used - bool doScroll = false; - - if (frame.bottom > bounds.bottom) - { - newY = frame.bottom - bounds.Height(); - doScroll = true; - } - else if (frame.top < bounds.top) - { - newY = frame.top; - doScroll = true; - } - if (doScroll) - ScrollTo(bounds.left, newY); - } -} - -// InvalidateItem() only does a real invalidate if the index is valid or the -// force flag is turned on - -void BComboBox::ChoiceListView::InvalidateItem(int32 index, bool force) -{ - int32 choices = fParent->fChoiceList->CountChoices(); - if ((index >= 0 && index < choices) || force) { - Invalidate(ItemFrame(index)); - } -} - -// This method doesn't check the index to see if it is valid, it just returns -// the BRect that an item and the index would have if it existed. - -BRect BComboBox::ChoiceListView::ItemFrame(int32 index) -{ - BRect rect(Bounds()); - float h = LineHeight(); - rect.top = index * h; - rect.bottom = rect.top + h; - return rect; -} - -// The window must be locked before this method is called - -void BComboBox::ChoiceListView::AdjustScrollBar() -{ - BScrollBar *sb = ScrollBar(B_VERTICAL); - if (sb) { - float h = LineHeight(); - float max = h * fParent->fChoiceList->CountChoices(); - BRect frame(Frame()); - float diff = max - frame.Height(); - float prop = frame.Height() / max; - if (diff < 0) { - diff = 0.0; - prop = 1.0; - } - sb->SetSteps(h, h * (frame.IntegerHeight() / h)); - sb->SetRange(0.0, diff); - sb->SetProportion(prop); - } -} - - -float BComboBox::ChoiceListView::LineHeight() -{ - return fFontHeight.ascent + fFontHeight.descent + fFontHeight.leading + 2; -} - - -// ---------------------------------------------------------------------------- -// #pragma mark - - - -BComboBox::TextInput::TextInput(BRect rect, BRect textRect, ulong rMask, - ulong flags) - : BTextView(rect, "_input_", textRect, be_plain_font, NULL, rMask, flags), - fFilter(NULL) -{ - MakeResizable(true); - fInitialText = NULL; - fClean = false; -} - - -BComboBox::TextInput::TextInput(BMessage *data) - : BTextView(data), - fFilter(NULL) -{ - MakeResizable(true); - fInitialText = NULL; - fClean = false; -} - - -BComboBox::TextInput::~TextInput() -{ - free(fInitialText); -} - - -status_t -BComboBox::TextInput::Archive(BMessage* data, bool /*deep*/) const -{ - return BTextView::Archive(data); -} - - -BArchivable * -BComboBox::TextInput::Instantiate(BMessage* data) -{ - // XXX: is "TextInput" the correct name for this class? Perhaps - // BComboBox::TextInput? - if (!validate_instantiation(data, "TextInput")) - return NULL; - - return new TextInput(data); -} - - -void -BComboBox::TextInput::SetInitialText() -{ - if (fInitialText) { - free(fInitialText); - fInitialText = NULL; - } - if (Text()) - fInitialText = strdup(Text()); -} - - -void -BComboBox::TextInput::SetFilter(text_input_filter_hook hook) -{ - fFilter = hook; -} - - -void -BComboBox::TextInput::KeyDown(const char *bytes, int32 numBytes) -{ - BComboBox* cb; - uchar aKey = bytes[0]; - - switch (aKey) { - case B_RETURN: - cb = dynamic_cast(Parent()); - ASSERT(cb); - - if (!cb->IsEnabled()) - break; - - ASSERT(fInitialText); - if (strcmp(fInitialText, Text()) != 0) - cb->CommitValue(); - free(fInitialText); - fInitialText = strdup(Text()); - { - int32 end = TextLength(); - Select(end, end); - } - // hide popup window if it's showing when the user presses the - // enter key - if (cb->fPopupWindow && cb->fPopupWindow->Lock()) { - if (!cb->fPopupWindow->IsHidden()) { - cb->HidePopupWindow(); - } - cb->fPopupWindow->Unlock(); - } - break; - case B_TAB: -// cb = dynamic_castParent()); -// ASSERT(cb); -// if (cb->fAutoComplete && cb->fCompletionIndex >= 0) { -// int32 from, to; -// cb->fText->GetSelection(&from, &to); -// if (from == to) { -// // HACK: this should never happen. The rest of the class -// // should be fixed so that fCompletionIndex is set to -1 if the -// // text is modified -// printf("BComboBox::TextInput::KeyDown() -- HACK! this shouldn't happen!"); -// cb->fCompletionIndex = -1; -// } -// -// const char *text = cb->fText->Text(); -// BString prefix; -// prefix.Append(text, from); -// -// int32 match; -// const char *completion; -// if (cb->fChoiceList->GetMatch( prefix.String(), -// cb->fCompletionIndex + 1, -// &match, -// &completion) == B_OK) -// { -// cb->fText->Delete(); // delete the selection -// cb->fText->Insert(completion); -// cb->fText->Select(from, from + strlen(completion)); -// cb->fCompletionIndex = match; -// cb->Select(cb->fCompletionIndex); -// } else { -// //system_beep(); -// } -// } else { - BView::KeyDown(bytes, numBytes); -// } - break; -#if 0 - case B_UP_ARROW: // fall through - case B_DOWN_ARROW: - cb = dynamic_cast(Parent()); - ASSERT(cb); - if (cb->fChoiceList) { - cb = dynamic_cast(Parent()); - ASSERT(cb); - if (!(cb->fPopupWindow)) { - cb->fPopupWindow = cb->CreatePopupWindow(); - } - if (cb->fPopupWindow->Lock()) { - // show popup window, if needed - if (cb->fPopupWindow->IsHidden()) { - cb->ShowPopupWindow(); - } else { - printf("Whoa!!! Erroneously got up/down arrow key down in TextInput::KeyDown()!\n"); - } - int32 index = cb->CurrentSelection(); - int32 choices = cb->fChoiceList->CountChoices(); - // select something, if no selection - if (index < 0 && choices > 0) { - if (aKey == B_UP_ARROW) { - cb->Select(choices - 1); - } else { - cb->Select(0); - } - } - cb->fPopupWindow->Unlock(); - } - } - break; -#endif - case B_ESCAPE: - cb = dynamic_cast(Parent()); - ASSERT(cb); - if (cb->fChoiceList) - { - cb = dynamic_cast(Parent()); - ASSERT(cb); - if (cb->fPopupWindow && cb->fPopupWindow->Lock()) - { - if (!cb->fPopupWindow->IsHidden()) - cb->HidePopupWindow(); - - cb->fPopupWindow->Unlock(); - } - } - break; - case ',': - { - int32 startSel, endSel; - GetSelection(&startSel, &endSel); - int32 length = TextLength(); - if (endSel == length) - Select(endSel, endSel); - BTextView::KeyDown(bytes, numBytes); - } - break; - default: - BTextView::KeyDown(bytes, numBytes); - break; - } -} - - -void -BComboBox::TextInput::MakeFocus(bool state) -{ -//+ PRINT(("_BTextInput_::MakeFocus(state=%d, view=%s)\n", state, -//+ Parent()->Name())); - if (state == IsFocus()) - return; - - BComboBox* parent = dynamic_cast(Parent()); - ASSERT(parent); - - BTextView::MakeFocus(state); - - if (state) { - SetInitialText(); - fClean = true; // text hasn't been dirtied yet. - - BMessage *m; - if (Window() && (m = Window()->CurrentMessage()) != 0 - && m->what == B_KEY_DOWN) { - // we're being focused by a keyboard event, so - // select all... - SelectAll(); - } - } else { - ASSERT(fInitialText); - if (strcmp(fInitialText, Text()) != 0) - parent->CommitValue(); - - free(fInitialText); - fInitialText = NULL; - fClean = false; - BMessage *m; - if (Window() && (m = Window()->CurrentMessage()) != 0 && m->what == B_MOUSE_DOWN) - Select(0,0); - - // hide popup window if it's showing when the text input loses focus - if (parent->fPopupWindow && parent->fPopupWindow->Lock()) { - if (!parent->fPopupWindow->IsHidden()) - parent->HidePopupWindow(); - - parent->fPopupWindow->Unlock(); - } - } - - // make sure the focus indicator gets drawn or undrawn - if (Window()) { - BRect invalRect(Bounds()); - invalRect.InsetBy(-kTextInputMargin, -kTextInputMargin); - parent->Draw(invalRect); - parent->Flush(); - } -} - - -void -BComboBox::TextInput::FrameResized(float x, float y) -{ - BTextView::FrameResized(x, y); - AlignTextRect(); -} - - -void -BComboBox::TextInput::Paste(BClipboard *clipboard) -{ - BTextView::Paste(clipboard); - Invalidate(); -} - - -// What a hack... -void -BComboBox::TextInput::AlignTextRect() -{ - BRect bounds = Bounds(); - BRect textRect = TextRect(); - - switch (Alignment()) { - default: - case B_ALIGN_LEFT: - textRect.OffsetTo(B_ORIGIN); - break; - - case B_ALIGN_CENTER: - textRect.OffsetTo((bounds.Width() - textRect.Width()) / 2, - textRect.top); - break; - - case B_ALIGN_RIGHT: - textRect.OffsetTo(bounds.Width() - textRect.Width(), textRect.top); - break; - } - - SetTextRect(textRect); -} - - -void -BComboBox::TextInput::InsertText(const char *inText, int32 inLength, - int32 inOffset, const text_run_array *inRuns) -{ - char* ptr = NULL; - - // strip out any return characters - // limiting to a reasonable amount of chars for a text control. - // otherwise this code could malloc some huge amount which isn't good. - if (strpbrk(inText, "\r\n") && inLength <= 1024) { - int32 len = inLength; - ptr = (char *)malloc(len + 1); - if (ptr) { - strncpy(ptr, inText, len); - ptr[len] = '\0'; - - char *p = ptr; - - while (len--) { - if (*p == '\n') - *p = ' '; - else if (*p == '\r') - *p = ' '; - - p++; - } - } - } - - if (fFilter != NULL) - inText = fFilter(inText, inLength, inRuns); - BTextView::InsertText(ptr ? ptr : inText, inLength, inOffset, inRuns); - - BComboBox *parent = dynamic_cast(Parent()); - if (parent) { - if (parent->fModificationMessage) - parent->Invoke(parent->fModificationMessage); - - BMessage *msg; - parent->Window()->PostMessage(msg = new BMessage(kTextInputModifyMessage), - parent); - delete msg; - } - - if (ptr) - free(ptr); -} - - -void -BComboBox::TextInput::DeleteText(int32 fromOffset, int32 toOffset) -{ - BTextView::DeleteText(fromOffset, toOffset); - BComboBox *parent = dynamic_cast(Parent()); - if (parent) { - if (parent->fModificationMessage) - parent->Invoke(parent->fModificationMessage); - - BMessage *msg; - parent->Window()->PostMessage(msg = new BMessage(kTextInputModifyMessage), - parent); - delete msg; - } -} - - -// #pragma mark - - - -BComboBox::ComboBoxWindow::ComboBoxWindow(BComboBox *box) - : BWindow(BRect(0, 0, 10, 10), NULL, B_BORDERED_WINDOW_LOOK, - B_FLOATING_SUBSET_WINDOW_FEEL, B_NOT_MOVABLE | B_NOT_RESIZABLE - | B_NOT_CLOSABLE | B_NOT_ZOOMABLE | B_NOT_MINIMIZABLE - | B_WILL_ACCEPT_FIRST_CLICK | B_ASYNCHRONOUS_CONTROLS) -{ - fParent = box; - DoPosition(); - BWindow *parentWin = fParent->Window(); - if (parentWin) - AddToSubset(parentWin); - - BRect rect(Bounds()); - rect.right -= B_V_SCROLL_BAR_WIDTH; - fListView = new ChoiceListView(rect, fParent); - AddChild(fListView); - rect.left = rect.right; - rect.right += B_V_SCROLL_BAR_WIDTH; - fScrollBar = new BScrollBar(rect, "_popup_scroll_bar_", fListView, 0, 1000, - B_VERTICAL); - AddChild(fScrollBar); - fListView->AdjustScrollBar(); -} - - -BComboBox::ComboBoxWindow::~ComboBoxWindow() -{ - fListView->RemoveSelf(); - delete fListView; -} - - -void BComboBox::ComboBoxWindow::WindowActivated(bool /*active*/) -{ -// if (active) -// fListView->AdjustScrollBar(); -} - - -void BComboBox::ComboBoxWindow::FrameResized(float /*width*/, float /*height*/) -{ - fListView->AdjustScrollBar(); -} - - -void BComboBox::ComboBoxWindow::DoPosition() -{ - BRect winRect(fParent->fText->Frame()); - winRect = fParent->ConvertToScreen(winRect); -// winRect.left += fParent->Divider() + 5; - winRect.right -= 2; - winRect.OffsetTo(winRect.left, winRect.bottom + kTextInputMargin); - winRect.bottom = winRect.top + 100; - MoveTo(winRect.LeftTop()); - ResizeTo(winRect.IntegerWidth(), winRect.IntegerHeight()); -} - - -BComboBox::ChoiceListView *BComboBox::ComboBoxWindow::ListView() -{ - return fListView; -} - - -BScrollBar *BComboBox::ComboBoxWindow::ScrollBar() -{ - return fScrollBar; -} - - -// ---------------------------------------------------------------------------- -// #pragma mark - - - -BComboBox::BComboBox(BRect frame, const char *name, const char *label, - BMessage *message, uint32 resizeMask, uint32 flags) - : BControl(frame, name, label, message, resizeMask, - flags | B_WILL_DRAW | B_FRAME_EVENTS), - fPopupWindow(NULL), - fModificationMessage(NULL), - fChoiceList(0), - fLabelAlign(B_ALIGN_LEFT), - fAutoComplete(false), - fButtonDepressed(false), - fDepressedWhenClicked(false), - fTrackingButtonDown(false), - fFrameCache(frame) -{ - // If the user wants this control to be keyboard navigable, then we really - // want the underlying text view to be navigable, not this view. - bool navigate = ((Flags() & B_NAVIGABLE) != 0); - if (navigate) - { - fSkipSetFlags = true; - SetFlags(Flags() & ~B_NAVIGABLE); // disable navigation for this - fSkipSetFlags = false; - } - - fDivider = StringWidth(label); - - BRect rect(frame); - rect.OffsetTo(0, 0); - rect.left += fDivider + kLabelRightMargin; -// rect.right -= kButtonWidth + 1; -// rect.right; - rect.InsetBy(kTextInputMargin, kTextInputMargin); - BRect textRect(rect); - textRect.OffsetTo(0, 0); - textRect.left += 2; - textRect.right -= 2; - - fText = new TextInput(rect, textRect, B_FOLLOW_TOP | B_FOLLOW_LEFT_RIGHT, - B_WILL_DRAW | B_FRAME_EVENTS | (navigate ? B_NAVIGABLE : 0)); - float height = fText->LineHeight(); - rect.bottom = rect.top + height; -// fText->ResizeTo(rect.IntegerWidth(), height); - AddChild(fText); - - font_height fontInfo; - GetFontHeight(&fontInfo); - float h1 = ceil(fontInfo.ascent + fontInfo.descent + fontInfo.leading); - float h2 = fText->LineHeight(); - - // Height of main view must be the larger of h1 and h2+(TV_MARGIN*2) - float h = (h1 > h2 + (TV_MARGIN*2)) ? h1 : h2 + (TV_MARGIN*2); - BRect b = Bounds(); - ResizeTo(b.Width(), h); - b.bottom = h; - - // set height and position of text entry view - fText->ResizeTo(fText->Bounds().Width(), h2); - // vertically center this view - fText->MoveBy(0, (b.Height() - (h2+(TV_MARGIN*2))) / 2); - - rect.left = rect.right + 1; - rect.right = rect.left + kButtonWidth; - - fButtonRect = rect; - fTextEnd = 0; - fSelected = -1; - fCompletionIndex = -1; - fWinMovedFilter = new MovedMessageFilter(this); -} - - -BComboBox::~BComboBox() -{ - if (fPopupWindow && fPopupWindow->Lock()) - fPopupWindow->Quit(); - - RemoveChild(fText); - delete fText; - - if (fWinMovedFilter->Looper()) - fWinMovedFilter->Looper()->RemoveFilter(fWinMovedFilter); - - delete fWinMovedFilter; - -} - - -void BComboBox::SetChoiceList(BChoiceList *list) -{ -// delete fChoiceList; - fChoiceList = list; - ChoiceListUpdated(); -} - - -BChoiceList *BComboBox::ChoiceList() -{ - return fChoiceList; -} - - -void BComboBox::ChoiceListUpdated() -{ - if (fPopupWindow && fPopupWindow->Lock()) - { - if (!fPopupWindow->IsHidden()) - { - // do an invalidate on the choice list - fPopupWindow->ListView()->Invalidate(); - fPopupWindow->ListView()->AdjustScrollBar(); - // XXX: change the selection and select the proper item, if possible - } - fPopupWindow->Unlock(); - } -} - - -//void BComboBox::AddChoice(const char *text) -//{ -// fChoiceList.AddItem((char *)text); -// if (fPopupWindow && fPopupWindow->Lock()) { -// if (!fPopupWindow->IsHidden()) { -// // do an invalidate on the new item's location -// int32 index = CountChoices() - 1; -// fPopupWindow->ListView()->InvalidateItem(index); -// fPopupWindow->ListView()->AdjustScrollBar(); -// } -// fPopupWindow->Unlock(); -// } -//} - - -//const char *BComboBox::ChoiceAt(int32 index) -//{ -// return (const char *)fChoiceList.ItemAt(index); -//} - - -//int32 BComboBox::CountChoices() -//{ -// return fChoiceList.CountItems(); -//} - - -void -BComboBox::Select(int32 index, bool changeTextSelection) -{ - int32 oldIndex = fSelected; - if (index < fChoiceList->CountChoices() && index >= 0) { - BWindow *win = Window(); - bool gotLock = (win && win->Lock()); - if (!win || gotLock) { - fSelected = index; - if (fPopupWindow && fPopupWindow->Lock()) { - ChoiceListView *lv = fPopupWindow->ListView(); - lv->InvalidateItem(oldIndex); - lv->InvalidateItem(fSelected); - lv->ScrollToSelection(); - fPopupWindow->Unlock(); - } - - if (changeTextSelection) { - // Find last coma - const char *ptr = fText->Text(); - const char *end; - int32 tlength = fText->TextLength(); - - for (end = ptr+tlength-1; end>ptr; end--) { - if (*end == ',') { - // Find end of whitespace - for (end++; isspace(*end); end++) {} - break; - } - } - int32 soffset = end-ptr; - int32 eoffset = tlength; - if (end != 0) - fText->Delete(soffset, eoffset); - - tlength = strlen(fChoiceList->ChoiceAt(fSelected)); - fText->Insert(soffset, fChoiceList->ChoiceAt(fSelected), tlength); - eoffset = fText->TextLength(); - fText->Select(soffset, eoffset); -// fText->SetText(fChoiceList->ChoiceAt(fSelected)); -// fText->SelectAll(); - } - - if (gotLock) - win->Unlock(); - } - } else { - Deselect(); - return; - } -} - - -void -BComboBox::Deselect() -{ - BWindow *win = Window(); - bool gotLock = (win && win->Lock()); - if (!win || gotLock) { - int32 oldIndex = fSelected; - fSelected = -1; - // invalidate the old selected item, if needed - if (oldIndex >= 0 && fPopupWindow && fPopupWindow->Lock()) { - fPopupWindow->ListView()->InvalidateItem(oldIndex); - fPopupWindow->Unlock(); - } - - if (gotLock) - win->Unlock(); - } -} - - -int32 -BComboBox::CurrentSelection() -{ - return fSelected; -} - - -void -BComboBox::SetAutoComplete(bool on) -{ - fAutoComplete = on; -} - - -bool -BComboBox::GetAutoComplete() -{ - return fAutoComplete; -} - - -void -BComboBox::SetLabel(const char *text) -{ - BControl::SetLabel(text); - BRect invalRect = Bounds(); - invalRect.right = fDivider; - Invalidate(invalRect); -} - - -void -BComboBox::SetValue(int32 value) -{ - BControl::SetValue(value); -} - - -void -BComboBox::SetText(const char *text) -{ - fText->SetText(text); - if (fText->IsFocus()) - fText->SetInitialText(); - - fText->Invalidate(); -} - - -const char * -BComboBox::Text() const -{ - return fText->Text(); -} - - -int32 -BComboBox::TextLength() const -{ - return fText->TextLength(); -} - - -BTextView * -BComboBox::TextView() -{ - return fText; -} - - -void -BComboBox::SetDivider(float divide) -{ - float diff = fDivider - divide; - fDivider = divide; - - fText->MoveBy(-diff, 0); - fText->ResizeBy(diff, 0); - - if (Window()) { - fText->Invalidate(); - Invalidate(); - } -} - - -float -BComboBox::Divider() const -{ - return fDivider; -} - - -void -BComboBox::SetAlignment(alignment label, alignment text) -{ - fText->SetAlignment(text); - fText->AlignTextRect(); - - if (fLabelAlign != label) { - fLabelAlign = label; - Invalidate(); - } -} - - -void -BComboBox::GetAlignment(alignment *label, alignment *text) const -{ - *text = fText->Alignment(); - *label = fLabelAlign; -} - - -void -BComboBox::SetModificationMessage(BMessage *message) -{ - delete fModificationMessage; - fModificationMessage = message; -} - - -BMessage * -BComboBox::ModificationMessage() const -{ - return fModificationMessage; -} - - -void -BComboBox::SetFilter(text_input_filter_hook hook) -{ - fText->SetFilter(hook); -} - - -void -BComboBox::GetPreferredSize(float */*width*/, float */*height*/) -{ -// BFont font; -// GetFont(&font); -// -// *width = Bounds().IntegerWidth(); -// if (Label() != NULL) { -// float strWidth = font.StringWidth(Label()); -// *width = ceil(kTextInputMargin + strWidth + kLabelRightMargin + -// (strWidth * 1.50) + kTextInputMargin); -// } -// -// font_height finfo; -// float h1; -// float h2; -// -// font.GetHeight(&finfo); -// h1 = ceil(finfo.ascent + finfo.descent + finfo.leading); -// h2 = fText->LineHeight(); -// -// // Height of main view must be the larger of h1 and h2+(kTextInputMargin*2) -// *height = ceil((h1 > h2 + (kTextInputMargin*2)) ? h1 : h2 + (kTextInputMargin*2)); -} - - -void -BComboBox::ResizeToPreferred() -{ - BControl::ResizeToPreferred(); -} - - -void -BComboBox::FrameMoved(BPoint new_position) -{ - if (fPopupWindow && fPopupWindow->Lock()) { - fPopupWindow->MoveBy(new_position.x - fFrameCache.left, - new_position.y - fFrameCache.top); - fPopupWindow->Unlock(); - } - fFrameCache.OffsetTo(new_position); -} - - -void -BComboBox::FrameResized(float new_width, float new_height) -{ - // It's the cheese! - float dx = new_width - fFrameCache.Width(); - float dy = new_height - fFrameCache.Height(); - if (dx != 0 && Window()) { -// BRect inval(fFrameCache.right, fFrameCache.top, -// fFrameCache.right+dx, fFrameCache.bottom); - BRect inval(Bounds()); - if (dx > 0) - inval.left = inval.right-dx-1; - else - inval.left = inval.right-3; -// Window()->ConvertToScreen(&inval); -// ConvertFromScreen(&inval); - Invalidate(inval); - } - - fFrameCache.right += dx; - fFrameCache.bottom += dy; -// fButtonRect.OffsetBy(dx, 0); - - if (fPopupWindow && fPopupWindow->Lock()) { - if (!fPopupWindow->IsHidden()) - HidePopupWindow(); - - fPopupWindow->Unlock(); - } -} - - -void -BComboBox::WindowActivated(bool /*active*/) -{ - if (fText->IsFocus()) - Draw(Bounds()); -} - - -void -BComboBox::Draw(BRect /*updateRect*/) -{ - BRect bounds = Bounds(); - font_height fInfo; - rgb_color high = HighColor(); - rgb_color base = ViewColor(); - bool focused; - bool enabled; - rgb_color white = {255, 255, 255, 255}; - rgb_color black = { 0, 0, 0, 255 }; - - enabled = IsEnabled(); - focused = fText->IsFocus() && Window()->IsActive(); - - BRect fr = fText->Frame(); - - fr.InsetBy(-3, -3); - fr.bottom -= 1; - if (enabled) - SetHighColor(tint_color(base, B_DARKEN_1_TINT)); - else - SetHighColor(base); - - StrokeLine(fr.LeftBottom(), fr.LeftTop()); - StrokeLine(fr.RightTop()); - - if (enabled) - SetHighColor(white); - else - SetHighColor(tint_color(base, B_LIGHTEN_2_TINT)); - - StrokeLine(fr.LeftBottom()+BPoint(1,0), fr.RightBottom()); - StrokeLine(fr.RightTop()+BPoint(0,1)); - fr.InsetBy(1,1); - - if (focused) { - // draw UI indication for 'active' - SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR)); - StrokeRect(fr); - } else { - if (enabled) - SetHighColor(tint_color(base, B_DARKEN_4_TINT)); - else - SetHighColor(tint_color(base, B_DARKEN_2_TINT)); - StrokeLine(fr.LeftBottom(), fr.LeftTop()); - StrokeLine(fr.RightTop()); - SetHighColor(base); - StrokeLine(fr.LeftBottom()+BPoint(1,0), fr.RightBottom()); - StrokeLine(fr.RightTop()+BPoint(0,1)); - } - - fr.InsetBy(1,1); - - if (!enabled) - SetHighColor(tint_color(base, B_DISABLED_MARK_TINT)); - else - SetHighColor(white); - - StrokeRect(fr); - SetHighColor(high); - - bounds.right = bounds.left + fDivider; - if ((Label()) && (fDivider > 0.0)) { - BPoint loc; - GetFontHeight(&fInfo); - - switch (fLabelAlign) { - default: - case B_ALIGN_LEFT: - loc.x = bounds.left + TV_MARGIN; - break; - case B_ALIGN_CENTER: - { - float width = StringWidth(Label()); - float center = (bounds.right - bounds.left) / 2; - loc.x = center - (width/2); - break; - } - case B_ALIGN_RIGHT: - { - float width = StringWidth(Label()); - loc.x = bounds.right - width - TV_MARGIN; - break; - } - } - - uint32 rmode = ResizingMode(); - if ((rmode & _rule_(0xf, 0, 0xf, 0)) == _rule_(_VIEW_TOP_, 0, _VIEW_BOTTOM_, 0)) - loc.y = fr.bottom - 2; - else - loc.y = bounds.bottom - (2 + ceil(fInfo.descent)); - - MovePenTo(loc); - SetHighColor(black); - DrawString(Label()); - SetHighColor(high); - } -} - - -void -BComboBox::MessageReceived(BMessage *msg) -{ - switch (msg->what) { - case kTextInputModifyMessage: - TryAutoComplete(); - break; - case kPopupButtonInvokeMessage: - if (fChoiceList && fChoiceList->CountChoices() && !fPopupWindow) - fPopupWindow = CreatePopupWindow(); - - if (fPopupWindow->Lock()) { - if (fPopupWindow->IsHidden()) - ShowPopupWindow(); - else - HidePopupWindow(); - - fPopupWindow->Unlock(); - } - break; - case kWindowMovedMessage: - if (fPopupWindow && fPopupWindow->Lock()) { - if (!fPopupWindow->IsHidden()) - HidePopupWindow(); - - fPopupWindow->Unlock(); - } - break; - default: - BControl::MessageReceived(msg); - } -} - - -void -BComboBox::MouseDown(BPoint where) -{ -// printf("BComboBox::MouseDown(%f, %f)\n", where.x, where.y); - /*if (fButtonRect.Contains(where)) { // clicked in button area - fDepressedWhenClicked = fButtonDepressed; - fButtonDepressed = !fButtonDepressed; - fTrackingButtonDown = true; - SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS); - Invalidate(fButtonRect); - fText->MakeFocus(true); - }*/ - BControl::MouseDown(where); -} - - -void -BComboBox::MouseUp(BPoint /*where*/) -{ - if (fTrackingButtonDown) { - // send an invoke message when the button changes state - if (fButtonDepressed != fDepressedWhenClicked) { - BMessage *msg; - Window()->PostMessage(msg = new BMessage(kPopupButtonInvokeMessage),this); - delete msg; - } - fTrackingButtonDown = false; - } -} - - -void -BComboBox::MouseMoved(BPoint where, uint32 /*transit*/,const BMessage */*dragMessage*/) -{ - if (fTrackingButtonDown) { - BRect sloppyRect = fButtonRect; - sloppyRect.InsetBy(-3, -3); - - bool oldState = fButtonDepressed; - fButtonDepressed = sloppyRect.Contains(where) ? !fDepressedWhenClicked - : fDepressedWhenClicked; - - if (oldState != fButtonDepressed) - Invalidate(fButtonRect); - } -} - - -status_t -BComboBox::Invoke(BMessage *msg) -{ - return BControl::Invoke(msg); -} - - -void -BComboBox::AttachedToWindow() -{ - Window()->AddFilter(fWinMovedFilter); - if (Parent()) { - SetViewColor(Parent()->ViewColor()); - SetLowColor(ViewColor()); - } - - bool enabled = IsEnabled(); - rgb_color mc = HighColor(); - rgb_color base; - BFont textFont; - - // mc used to be base in this line - if (mc.red == 255 && mc.green == 255 && mc.blue == 255) - base = ViewColor(); - else - base = LowColor(); - - fText->GetFontAndColor(0, &textFont); - mc = enabled ? mc : disable_color(base); - - fText->SetFontAndColor(&textFont, B_FONT_ALL, &mc); - - if (!enabled) - base = tint_color(base, B_DISABLED_MARK_TINT); - else - base.red = base.green = base.blue = 255; - - fText->SetLowColor(base); - fText->SetViewColor(base); - - fText->MakeEditable(enabled); -} - - -void -BComboBox::DetachedFromWindow() -{ - fWinMovedFilter->Looper()->RemoveFilter(fWinMovedFilter); -} - - -void -BComboBox::SetFlags(uint32 flags) -{ - if (!fSkipSetFlags) { - uint32 te_flags = fText->Flags(); - bool te_nav = ((te_flags & B_NAVIGABLE) != 0); - bool wants_nav = ((flags & B_NAVIGABLE) != 0); - - // the ComboBox should never be navigable - ASSERT((Flags() & B_NAVIGABLE) == 0); - - if (!te_nav && wants_nav) { - // The combo box wants to be navigable. Pass that along to - // the text view - fText->SetFlags(te_flags | B_NAVIGABLE); - } else if (te_nav && !wants_nav) { - // Caller wants to end NAV on the text view; - fText->SetFlags(te_flags & ~B_NAVIGABLE); - } - - flags = flags & ~B_NAVIGABLE; // never want NAV for the combo box - } - BControl::SetFlags(flags); -} - - -void -BComboBox::SetEnabled(bool enabled) -{ - if (enabled == IsEnabled()) - return; - - if (Window()) { - fText->MakeEditable(enabled); - rgb_color mc = HighColor(); - rgb_color base = ViewColor(); - - mc = (enabled) ? mc : disable_color(base); - BFont textFont; - fText->GetFontAndColor(0, &textFont); - fText->SetFontAndColor(&textFont, B_FONT_ALL, &mc); - - if (!enabled) - base = tint_color(base, B_DISABLED_MARK_TINT); - else - base.red = base.green = base.blue = 255; - - fText->SetLowColor(base); - fText->SetViewColor(base); - - fText->Invalidate(); - Window()->UpdateIfNeeded(); - } - - fSkipSetFlags = true; - BControl::SetEnabled(enabled); - fSkipSetFlags = false; - -//+ // Want the sub_view to be the navigable one. We always want to be able -//+ // to navigate to that view, even if disabled since Copy still works. -//+ fText->SetFlags(fText->Flags() | B_NAVIGABLE); -//+ SetFlags(Flags() & ~B_NAVIGABLE); -} - - -//void BComboBox::AllAttached() -//{ -//} - - -BComboBox::ComboBoxWindow* -BComboBox::CreatePopupWindow() -{ - ComboBoxWindow *win = new ComboBoxWindow(this); - return win; -} - - -void -BComboBox::CommitValue() -{ - Invoke(); -} - - -void -BComboBox::TryAutoComplete() -{ - int32 from, to; - fText->GetSelection(&from, &to); - if (fAutoComplete && from == to) { - bool autoCompleted = false; - const char *ptr = fText->Text(); - if (to > fTextEnd && from == fText->TextLength()) { - const char *completion; - // find the first matching choice and do auto-completion - - // Find last comma - const char *end; - for (end = fText->Text()+fText->TextLength()-1; end>ptr; end--) { - if (*end == ',') { - // Find end of whitespace - for (end++; isspace(*end); end++) {} - if (*end == 0) - return; - break; - } - } - if (fChoiceList->GetMatch(end, 0, &fCompletionIndex, &completion) == B_OK) { - fText->Insert(completion); - fText->Select(to, to + strlen(completion)); - Select(fCompletionIndex); - autoCompleted = true; - } else - fCompletionIndex = -1; - } - fTextEnd = to; - - if (!autoCompleted) { - int32 sel = CurrentSelection(); - if (sel >= 0) { - const char *selText = fChoiceList->ChoiceAt(sel); - if (selText && !strcmp(ptr, selText)) { - // don't Deselect() if the text input matches the selection - return; - } - } - fCompletionIndex = -1; - Deselect(); - } - } -} - - -// fPopupWindow must exist and already be locked & hidden when this function -// is called -void -BComboBox::ShowPopupWindow() -{ - // adjust position of the popup window - fPopupWindow->DoPosition(); - fPopupWindow->ListView()->SetEventMask(B_POINTER_EVENTS, 0); - fPopupWindow->Show(); - fPopupWindow->ListView()->MakeFocus(true); -} - - -// fPopupWindow must exist and already be locked & shown when this function -// is called -void -BComboBox::HidePopupWindow() -{ - fPopupWindow->Hide(); - fPopupWindow->ListView()->SetEventMask(0, 0); - fButtonDepressed = false; - Invalidate(fButtonRect); -} - - -void -BComboBox::MakeFocus(bool state) -{ - fText->MakeFocus(state); - if (state) - fText->SelectAll(); -} - - -// #pragma mark - - - -BComboBox::MovedMessageFilter::MovedMessageFilter(BHandler *target) - : BMessageFilter(B_WINDOW_MOVED) -{ - fTarget = target; -} - - -filter_result -BComboBox::MovedMessageFilter::Filter(BMessage *message,BHandler **/*target*/) -{ - BMessage *dup = new BMessage(*message); - dup->what = kWindowMovedMessage; - if (fTarget->Looper()) - fTarget->Looper()->PostMessage(dup, fTarget); - - delete dup; - return B_DISPATCH_MESSAGE; -} - - -// #pragma mark - - - -BDefaultChoiceList::BDefaultChoiceList(BComboBox *owner) -{ - fOwner = owner; - fList = new StringObjectList(); -} - - -BDefaultChoiceList::~BDefaultChoiceList() -{ - BString *string; - while ((string = fList->RemoveItemAt(0)) != NULL) { - delete string; - } - - delete fList; -} - - -const char* -BDefaultChoiceList::ChoiceAt(int32 index) -{ - BString *string = fList->ItemAt(index); - if (string) - return string->String(); - - return NULL; -} - - -status_t -BDefaultChoiceList::GetMatch(const char *prefix, int32 startIndex, - int32 *matchIndex, const char **completionText) -{ - BString *str; - int32 len = strlen(prefix); - int32 choices = fList->CountItems(); - - for (int32 i = startIndex; i < choices; i++) { - str = fList->ItemAt(i); - if (!str->ICompare(prefix, len)) { - // prefix matches - *matchIndex = i; - *completionText = str->String() + len; - return B_OK; - } - } - *matchIndex = -1; - *completionText = NULL; - return B_ERROR; -} - - -int32 -BDefaultChoiceList::CountChoices() -{ - return fList->CountItems(); -} - - -status_t -BDefaultChoiceList::AddChoice(const char *toAdd) -{ - BString *str = new BString(toAdd); - bool r = fList->AddItem(str); - if (fOwner) - fOwner->ChoiceListUpdated(); - - return (r) ? B_OK : B_ERROR; -} - - -status_t -BDefaultChoiceList::AddChoiceAt(const char *toAdd, int32 index) -{ - BString *str = new BString(toAdd); - bool r = fList->AddItem(str, index); - if (fOwner) - fOwner->ChoiceListUpdated(); - - return r ? B_OK : B_ERROR; -} - - -//int BStringCompareFunction(const BString *s1, const BString *s2) -//{ -// return s1->Compare(s2); -//} - - -status_t -BDefaultChoiceList::RemoveChoice(const char *toRemove) -{ - BString *string; - int32 choices = fList->CountItems(); - for (int32 i = 0; i < choices; i++) { - string = fList->ItemAt(i); - if (!string->Compare(toRemove)) { - fList->RemoveItemAt(i); - if (fOwner) - fOwner->ChoiceListUpdated(); - - return B_OK; - } - } - return B_ERROR; -} - - -status_t -BDefaultChoiceList::RemoveChoiceAt(int32 index) -{ - BString *string = fList->RemoveItemAt(index); - if (string) { - delete string; - if (fOwner) - fOwner->ChoiceListUpdated(); - - return B_OK; - } - return B_ERROR; -} - - -void -BDefaultChoiceList::SetOwner(BComboBox *owner) -{ - fOwner = owner; -} - - -BComboBox* -BDefaultChoiceList::Owner() -{ - return fOwner; -} - diff --git a/src/apps/mail/ComboBox.h b/src/apps/mail/ComboBox.h deleted file mode 100644 index 1ab8886c3c..0000000000 --- a/src/apps/mail/ComboBox.h +++ /dev/null @@ -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 -#include - -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 - diff --git a/src/apps/mail/Content.cpp b/src/apps/mail/Content.cpp index b025ae2275..7d2385d897 100644 --- a/src/apps/mail/Content.cpp +++ b/src/apps/mail/Content.cpp @@ -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(); diff --git a/src/apps/mail/Content.h b/src/apps/mail/Content.h index 2465801406..5f3b39d06f 100644 --- a/src/apps/mail/Content.h +++ b/src/apps/mail/Content.h @@ -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; diff --git a/src/apps/mail/Header.cpp b/src/apps/mail/Header.cpp index 7ce744baa5..48656128e6 100644 --- a/src/apps/mail/Header.cpp +++ b/src/apps/mail/Header.cpp @@ -32,22 +32,14 @@ countries. Other brand product names are registered trademarks or trademarks of their respective holders. All rights reserved. */ -#include "MailApp.h" -#include "MailSupport.h" -#include "MailWindow.h" -#include "Messages.h" + #include "Header.h" -#include "Utilities.h" -#include "QueryMenu.h" -#include "FieldMsg.h" -#include "Prefs.h" -#include -#include +#include -#include -#include +#include #include +#include #include #include #include @@ -63,12 +55,17 @@ of their respective holders. All rights reserved. #include #include -#include -#include -#include -#include -#include -#include +#include +#include + +#include "MailApp.h" +#include "MailSupport.h" +#include "MailWindow.h" +#include "Messages.h" +#include "Utilities.h" +#include "QueryMenu.h" +#include "FieldMsg.h" +#include "Prefs.h" #define B_TRANSLATION_CONTEXT "Mail" @@ -78,31 +75,9 @@ using namespace BPrivate; using std::map; -const char* kDateLabel = B_TRANSLATE("Date:"); const uint32 kMsgFrom = 'hFrm'; -const uint32 kMsgEncoding = 'encd'; const uint32 kMsgAddressChosen = 'acsn'; -static const float kTextControlDividerOffset = 0; -static const float kMenuFieldDividerOffset = 6; - - -class QPopupMenu : public QueryMenu { - public: - QPopupMenu(const char *title); - - private: - void AddPersonItem(const entry_ref *ref, ino_t node, BString &name, - BString &email, const char *attr, BMenu *groupMenu, - BMenuItem *superItem); - - protected: - virtual void EntryCreated(const entry_ref &ref, ino_t node); - virtual void EntryRemoved(ino_t node); - - int32 fGroups; // Current number of "group" submenus. Includes All People if present. -}; - struct CompareBStrings { bool @@ -113,235 +88,84 @@ struct CompareBStrings { }; -const char* -mail_to_filter(const char* text, int32& length, const text_run_array*& runs) -{ - if (!strncmp(text, "mailto:", 7)) { - text += 7; - length -= 7; - if (runs != NULL) - runs = NULL; - } +class LabelView : public BStringView { +public: + LabelView(const char* label); - return text; + bool IsEnabled() const + { return fEnabled; } + void SetEnabled(bool enabled); + + virtual void Draw(BRect updateRect); + +private: + bool fEnabled; +}; + + +// #pragma mark - LabelView + + +LabelView::LabelView(const char* label) + : + BStringView("label", label), + fEnabled(true) +{ + SetAlignment(B_ALIGN_RIGHT); } -static const float kPlainFontSizeScale = 0.9; - - -// #pragma mark - THeaderView - - -THeaderView::THeaderView(BRect rect, BRect windowRect, bool incoming, - bool resending, uint32 defaultCharacterSet, int32 defaultAccount) - : - BBox(rect, "m_header", B_FOLLOW_LEFT_RIGHT, B_WILL_DRAW, B_NO_BORDER), - - fAccountMenu(NULL), - fEncodingMenu(NULL), - fAccountID(defaultAccount), - fAccountTo(NULL), - fAccount(NULL), - fBcc(NULL), - fCc(NULL), - fSubject(NULL), - fTo(NULL), - fDateLabel(NULL), - fDate(NULL), - fIncoming(incoming), - fCharacterSetUserSees(defaultCharacterSet), - fResending(resending), - fBccMenu(NULL), - fCcMenu(NULL), - fToMenu(NULL), - fEmailList(NULL) +void +LabelView::SetEnabled(bool enabled) { - BMenuField* field; - BMessage* msg; - - BString kAttachments(B_TRANSLATE("Attachments: ")); - BString kDecoding(B_TRANSLATE("Decoding:")); - BString kFrom(B_TRANSLATE("From:")); - BString kTo(B_TRANSLATE("To:")); - BString kEncoding(B_TRANSLATE("Encoding:")); - BString kAccount(B_TRANSLATE("Account:")); - BString kCc(B_TRANSLATE("Cc:")); - BString kSubject(B_TRANSLATE("Subject:")); - BString kBcc(B_TRANSLATE("Bcc:")); - - BObjectList kToCompare; - kToCompare.AddItem(&kAttachments); - kToCompare.AddItem(&kDecoding); - kToCompare.AddItem(&kFrom); - kToCompare.AddItem(&kTo); - kToCompare.AddItem(&kEncoding); - kToCompare.AddItem(&kAccount); - kToCompare.AddItem(&kCc); - kToCompare.AddItem(&kSubject); - kToCompare.AddItem(&kBcc); - - float x = 0; - // Get the longest translated string's width to use when calculating - // horizontal positions - for(int i = 0; i < kToCompare.CountItems(); ++i) { - float stringWidth = StringWidth(kToCompare.ItemAt(i)->String()) + 9; - if (stringWidth > x) - x = stringWidth; + if (enabled != fEnabled) { + fEnabled = enabled; + Invalidate(); } - float y = TO_FIELD_V; - - BMenuBar* dummy = new BMenuBar(BRect(0, 0, 100, 15), "Dummy"); - AddChild(dummy); - float width, menuBarHeight; - dummy->GetPreferredSize(&width, &menuBarHeight); - dummy->RemoveSelf(); - delete dummy; +} - float menuFieldHeight = menuBarHeight + 2; - float controlHeight = menuBarHeight + floorf(be_plain_font->Size() / 1.15); - if (!fIncoming) { - InitEmailCompletion(); - InitGroupCompletion(); +void +LabelView::Draw(BRect updateRect) +{ + if (Text() != NULL) { + BRect rect = Bounds(); + + rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR); + uint32 flags = 0; + if (!IsEnabled()) + flags |= BControlLook::B_DISABLED; + + be_control_look->DrawLabel(this, Text(), rect, updateRect, + base, flags, BAlignment(Alignment(), B_ALIGN_MIDDLE)); + } +} + + +// #pragma mark - THeaderView + + +THeaderView::THeaderView(bool incoming, bool resending, int32 defaultAccount) + : + fAccountMenu(NULL), + fAccountID(defaultAccount), + fAccount(NULL), + fFromControl(NULL), + fBccControl(NULL), + fDateView(NULL), + fIncoming(incoming), + fResending(resending) +{ + // From + if (fIncoming) { + fFromControl = new BTextControl(B_TRANSLATE("From:"), NULL, NULL); + fFromControl->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT); + fFromControl->SetEnabled(false); } - // Prepare the character set selection pop-up menu (we tell the user that - // it is the Encoding menu, even though it is really the character set). - // It may appear in the first line, to the right of the From box if the - // user is reading an e-mail. It appears on the second line, to the right - // of the e-mail account menu, if the user is composing a message. It lets - // the user quickly select a character set different from the application - // wide default one, and also shows them which character set is active. If - // you are reading a message, you also see an item that says "Automatic" - // for automatic decoding character set choice. It can slide around as the - // window is resized when viewing a message, but not when composing - // (because the adjacent pop-up menu can't resize dynamically due to a BeOS - // bug). - - float widestCharacterSet = 0; - bool markedCharSet = false; - BMenuItem* item; - - fEncodingMenu = new BPopUpMenu(B_EMPTY_STRING); - - BCharacterSetRoster roster; - BCharacterSet charset; - while (roster.GetNextCharacterSet(&charset) == B_OK) { - BString name(charset.GetPrintName()); - const char* mime = charset.GetMIMEName(); - if (mime) - name << " (" << mime << ")"; - - uint32 convertID; - if (mime == NULL || strcasecmp(mime, "UTF-8") != 0) - convertID = charset.GetConversionID(); - else - convertID = B_MAIL_UTF8_CONVERSION; - - msg = new BMessage(kMsgEncoding); - msg->AddInt32("charset", convertID); - fEncodingMenu->AddItem(item = new BMenuItem(name.String(), msg)); - if (convertID == fCharacterSetUserSees && !markedCharSet) { - item->SetMarked(true); - markedCharSet = true; - } - if (StringWidth(name.String()) > widestCharacterSet) - widestCharacterSet = StringWidth(name.String()); - } - - msg = new BMessage(kMsgEncoding); - msg->AddInt32("charset", B_MAIL_US_ASCII_CONVERSION); - fEncodingMenu->AddItem(item = new BMenuItem("US-ASCII", msg)); - if (fCharacterSetUserSees == B_MAIL_US_ASCII_CONVERSION && !markedCharSet) { - item->SetMarked(true); - markedCharSet = true; - } - - if (!resending && fIncoming) { - // reading a message, display the Automatic item - fEncodingMenu->AddSeparatorItem(); - msg = new BMessage(kMsgEncoding); - msg->AddInt32("charset", B_MAIL_NULL_CONVERSION); - fEncodingMenu->AddItem(item = new BMenuItem(B_TRANSLATE("Automatic"), msg)); - if (!markedCharSet) - item->SetMarked(true); - } - - // First line of the header, From for reading e-mails (includes the - // character set choice at the right), To when composing (nothing else in - // the row). - - BRect r; - char string[20]; - if (fIncoming && !resending) { - // Set up the character set pop-up menu on the right of "To" box. - r.Set (windowRect.Width() - widestCharacterSet - - StringWidth (kDecoding.String()) - 2 * SEPARATOR_MARGIN, - y - 2, windowRect.Width() - SEPARATOR_MARGIN, - y + menuFieldHeight); - field = new BMenuField (r, "decoding", kDecoding.String(), - fEncodingMenu, true /* fixedSize */, - B_FOLLOW_TOP | B_FOLLOW_RIGHT, - B_WILL_DRAW | B_NAVIGABLE | B_NAVIGABLE_JUMP); - field->SetDivider(field->StringWidth(kDecoding.String()) + 5); - AddChild(field); - r.Set(SEPARATOR_MARGIN, y, - field->Frame().left - SEPARATOR_MARGIN, y + menuFieldHeight); - sprintf(string, kFrom.String()); - } else { - r.Set(x - 12, y, windowRect.Width() - SEPARATOR_MARGIN, - y + menuFieldHeight); - string[0] = 0; - } - - y += controlHeight; - fTo = new TTextControl(r, string, new BMessage(TO_FIELD), fIncoming, - resending, B_FOLLOW_LEFT_RIGHT); - fTo->SetFilter(mail_to_filter); - + // From accounts menu + BMenuField* fromField = NULL; if (!fIncoming || resending) { - fTo->SetChoiceList(&fEmailList); - fTo->SetAutoComplete(true); - } else { - fTo->SetDivider(x - 12 - SEPARATOR_MARGIN); - fTo->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT); - } - - AddChild(fTo); - msg = new BMessage(FIELD_CHANGED); - msg->AddInt32("bitmask", FIELD_TO); - fTo->SetModificationMessage(msg); - - if (!fIncoming || resending) { - r.right = r.left - 5; - r.left = r.right - ceilf(be_plain_font->StringWidth( - kTo.String()) + 25); - r.top -= 1; - fToMenu = new QPopupMenu(kTo.String()); - field = new BMenuField(r, "", "", fToMenu, true, - B_FOLLOW_LEFT | B_FOLLOW_TOP, B_WILL_DRAW); - field->SetDivider(0.0); - field->SetEnabled(true); - AddChild(field); - } - - // "From:" accounts Menu and Encoding Menu. - if (!fIncoming || resending) { - // Put the character set box on the right of the From field. - r.Set(windowRect.Width() - widestCharacterSet - - StringWidth(kEncoding.String()) - 2 * SEPARATOR_MARGIN, - y - 2, windowRect.Width() - SEPARATOR_MARGIN, y + menuFieldHeight); - BMenuField* encodingField = new BMenuField(r, "encoding", - kEncoding.String(), fEncodingMenu, true /* fixedSize */, - B_FOLLOW_TOP | B_FOLLOW_RIGHT, - B_WILL_DRAW | B_NAVIGABLE | B_NAVIGABLE_JUMP); - encodingField->SetDivider(encodingField->StringWidth( - kEncoding.String()) + 5); - AddChild(encodingField); - - field = encodingField; - // And now the "from account" pop-up menu, on the left side, taking the // remaining space. @@ -355,7 +179,7 @@ THeaderView::THeaderView(BRect rect, BRect windowRect, bool incoming, name << ": " << account->RealName() << " <" << account->ReturnAddress() << ">"; - msg = new BMessage(kMsgFrom); + BMessage* msg = new BMessage(kMsgFrom); BMenuItem *item = new BMenuItem(name, msg); msg->AddInt32("id", account->AccountID()); @@ -385,274 +209,301 @@ THeaderView::THeaderView(BRect rect, BRect windowRect, bool incoming, app->SetDefaultAccount(fAccountID); } - r.Set(SEPARATOR_MARGIN, y - 2, - field->Frame().left - SEPARATOR_MARGIN, y + menuFieldHeight); - field = new BMenuField(r, "account", kFrom.String(), - fAccountMenu, true /* fixedSize */, - B_FOLLOW_TOP | B_FOLLOW_LEFT_RIGHT, - B_WILL_DRAW | B_NAVIGABLE | B_NAVIGABLE_JUMP); - AddChild(field, encodingField); - field->SetDivider(x - 12 - SEPARATOR_MARGIN + kMenuFieldDividerOffset); - field->SetAlignment(B_ALIGN_RIGHT); - y += controlHeight; - } else { - // To: account - bool account = BMailAccounts().CountAccounts() > 0; - - r.Set(SEPARATOR_MARGIN, y, - windowRect.Width() - SEPARATOR_MARGIN, y + menuFieldHeight); - if (account) - r.right -= SEPARATOR_MARGIN + ACCOUNT_FIELD_WIDTH; - fAccountTo = new TTextControl(r, kTo.String(), NULL, fIncoming, - false, B_FOLLOW_LEFT_RIGHT); - fAccountTo->SetEnabled(false); - fAccountTo->SetDivider(x - 12 - SEPARATOR_MARGIN); - fAccountTo->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT); - AddChild(fAccountTo); - - if (account) { - r.left = r.right + 6; r.right = windowRect.Width() - SEPARATOR_MARGIN; - fAccount = new TTextControl(r, kAccount.String(), NULL, - fIncoming, false, B_FOLLOW_RIGHT | B_FOLLOW_TOP); - fAccount->SetEnabled(false); - AddChild(fAccount); - } - y += controlHeight; + fromField = new BMenuField("account", B_TRANSLATE("From:"), + fAccountMenu, B_WILL_DRAW | B_NAVIGABLE | B_NAVIGABLE_JUMP); + fromField->SetAlignment(B_ALIGN_RIGHT); } - + + // To + fToLabel = new LabelView(B_TRANSLATE("To:")); + fToControl = new AddressTextControl(B_TRANSLATE("To:"), + new BMessage(TO_FIELD)); + if (fIncoming || resending) { + fToLabel->SetEnabled(false); + fToControl->SetEditable(false); + fToControl->SetEnabled(false); + } + + BMessage* msg = new BMessage(FIELD_CHANGED); + msg->AddInt32("bitmask", FIELD_TO); + fToControl->SetModificationMessage(msg); + + // Carbon copy + fCcLabel = new LabelView(B_TRANSLATE("Cc:")); + fCcControl = new AddressTextControl("cc", new BMessage(CC_FIELD)); if (fIncoming) { - --y; - r.Set(SEPARATOR_MARGIN, y, - windowRect.Width() - SEPARATOR_MARGIN, y + menuFieldHeight); - y += controlHeight; - fCc = new TTextControl(r, kCc.String(), - NULL, fIncoming, false, B_FOLLOW_LEFT_RIGHT); - fCc->SetEnabled(false); - fCc->SetDivider(x - 12 - SEPARATOR_MARGIN); - fCc->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT); - AddChild(fCc); + fCcLabel->SetEnabled(false); + fCcControl->SetEditable(false); + fCcControl->SetEnabled(false); + fCcControl->Hide(); + fCcLabel->Hide(); } + msg = new BMessage(FIELD_CHANGED); + msg->AddInt32("bitmask", FIELD_CC); + fCcControl->SetModificationMessage(msg); - --y; - r.Set(SEPARATOR_MARGIN, y, - windowRect.Width() - SEPARATOR_MARGIN, y + menuFieldHeight); - y += controlHeight; - fSubject = new TTextControl(r, kSubject.String(), - new BMessage(SUBJECT_FIELD),fIncoming, false, B_FOLLOW_LEFT_RIGHT); - AddChild(fSubject); - (msg = new BMessage(FIELD_CHANGED))->AddInt32("bitmask", FIELD_SUBJECT); - fSubject->SetModificationMessage(msg); - fSubject->SetDivider(x - 12 - SEPARATOR_MARGIN); - fSubject->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT); - if (fResending) - fSubject->SetEnabled(false); - - --y; - + // Blind carbon copy if (!fIncoming) { - r.Set(x - 12, y, CC_FIELD_H + CC_FIELD_WIDTH, y + menuFieldHeight); - fCc = new TTextControl(r, "", new BMessage(CC_FIELD), fIncoming, false); - fCc->SetFilter(mail_to_filter); - fCc->SetChoiceList(&fEmailList); - fCc->SetAutoComplete(true); - AddChild(fCc); - (msg = new BMessage(FIELD_CHANGED))->AddInt32("bitmask", FIELD_CC); - fCc->SetModificationMessage(msg); - - r.right = r.left - 5; - r.left = r.right - ceilf(be_plain_font->StringWidth( - kCc.String()) + 25); - r.top -= 1; - fCcMenu = new QPopupMenu(kCc.String()); - field = new BMenuField(r, "", "", fCcMenu, true, - B_FOLLOW_LEFT | B_FOLLOW_TOP, B_WILL_DRAW); - - field->SetDivider(0.0); - field->SetEnabled(true); - AddChild(field); - - r.Set(BCC_FIELD_H + be_plain_font->StringWidth(kBcc.String()), y, - windowRect.Width() - SEPARATOR_MARGIN, y + menuFieldHeight); - y += controlHeight; - fBcc = new TTextControl(r, "", new BMessage(BCC_FIELD), - fIncoming, false, B_FOLLOW_LEFT_RIGHT); - fBcc->SetFilter(mail_to_filter); - fBcc->SetChoiceList(&fEmailList); - fBcc->SetAutoComplete(true); - AddChild(fBcc); - (msg = new BMessage(FIELD_CHANGED))->AddInt32("bitmask", FIELD_BCC); - fBcc->SetModificationMessage(msg); - - r.right = r.left - 5; - r.left = r.right - ceilf(be_plain_font->StringWidth( - kBcc.String()) + 25); - r.top -= 1; - fBccMenu = new QPopupMenu(kBcc.String()); - field = new BMenuField(r, "", "", fBccMenu, true, - B_FOLLOW_LEFT | B_FOLLOW_TOP, B_WILL_DRAW); - field->SetDivider(0.0); - field->SetEnabled(true); - AddChild(field); - } else { - y -= SEPARATOR_MARGIN; - r.Set(SEPARATOR_MARGIN, y, x - 12 - 1, y + menuFieldHeight); - fDateLabel = new BStringView(r, "", kDateLabel); - fDateLabel->SetAlignment(B_ALIGN_RIGHT); - AddChild(fDateLabel); - fDateLabel->SetHighColor(0, 0, 0); - - r.Set(r.right + 9, y, windowRect.Width() - SEPARATOR_MARGIN, - y + menuFieldHeight); - fDate = new BStringView(r, "", ""); - AddChild(fDate); - fDate->SetHighColor(0, 0, 0); - - y += controlHeight + 5; + fBccControl = new AddressTextControl("bcc", new BMessage(BCC_FIELD)); + msg = new BMessage(FIELD_CHANGED); + msg->AddInt32("bitmask", FIELD_BCC); + fBccControl->SetModificationMessage(msg); } - ResizeTo(Bounds().Width(), y); + + // Subject + fSubjectControl = new BTextControl(B_TRANSLATE("Subject:"), NULL, + new BMessage(SUBJECT_FIELD)); + msg = new BMessage(FIELD_CHANGED); + msg->AddInt32("bitmask", FIELD_SUBJECT); + fSubjectControl->SetModificationMessage(msg); + fSubjectControl->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT); + if (fIncoming || fResending) + fSubjectControl->SetEnabled(false); + + // Date + LabelView* dateLabel = NULL; + if (fIncoming) { + dateLabel = new LabelView(B_TRANSLATE("Date:")); + dateLabel->SetEnabled(false); + fDateView = new BStringView("", ""); + fDateView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)); + } + + BGridLayout* layout = GridLayout(); + + layout->SetInsets(B_USE_DEFAULT_SPACING); + layout->SetVerticalSpacing(B_USE_HALF_ITEM_SPACING); + + int32 row = 0; + if (fromField != NULL) { + layout->AddItem(fromField->CreateLabelLayoutItem(), 0, row); + layout->AddItem(fromField->CreateMenuBarLayoutItem(), 1, row++, 3, 1); + } else if (fFromControl != NULL) { + layout->AddItem(fFromControl->CreateLabelLayoutItem(), 0, row); + layout->AddItem(fFromControl->CreateTextViewLayoutItem(), 1, row++, + 3, 1); + } + + layout->AddView(fToLabel, 0, row); + layout->AddView(fToControl, 1, row++, 3, 1); + layout->AddView(fCcLabel, 0, row); + layout->AddView(fCcControl, 1, row, fIncoming ? 3 : 1, 1); + if (fBccControl != NULL) { + layout->AddView(new LabelView(B_TRANSLATE("Bcc:")), 2, row); + layout->AddView(fBccControl, 3, row++); + } else + row++; + layout->AddItem(fSubjectControl->CreateLabelLayoutItem(), 0, row); + layout->AddItem(fSubjectControl->CreateTextViewLayoutItem(), 1, row++, + 3, 1); + + if (fDateView != NULL) { + layout->AddView(dateLabel, 0, row); + layout->AddView(fDateView, 1, row++, 3, 1); + } +} + + +const char* +THeaderView::From() const +{ + return fFromControl != NULL ? fFromControl->Text() : NULL; } void -THeaderView::InitEmailCompletion() +THeaderView::SetFrom(const char* from) { - // get boot volume - BVolume volume; - BVolumeRoster().GetBootVolume(&volume); + if (fFromControl != NULL) + fFromControl->SetText(from); +} - BQuery query; - query.SetVolume(&volume); - query.SetPredicate("META:email=**"); - // Due to R5 BFS bugs, you need two stars, META:email=** for the query. - // META:email="*" will just return one entry and stop, same with - // META:email=* and a few other variations. Grumble. - query.Fetch(); - entry_ref ref; - while (query.GetNextRef (&ref) == B_OK) { - BNode file; - if (file.SetTo(&ref) == B_OK) { - // Add the e-mail address as an auto-complete string. - BString email; - if (file.ReadAttrString("META:email", &email) >= B_OK) - fEmailList.AddChoice(email.String()); +bool +THeaderView::IsToEmpty() const +{ + return To() == NULL || To()[0] == '\0'; +} - // Also add the quoted full name as an auto-complete string. Can't - // do unquoted since auto-complete isn't that smart, so the user - // will have to type a quote mark if he wants to select someone by - // name. - BString fullName; - if (file.ReadAttrString("META:name", &fullName) >= B_OK) { - if (email.FindFirst('<') < 0) { - email.ReplaceAll('>', '_'); - email.Prepend("<"); - email.Append(">"); - } - fullName.ReplaceAll('\"', '_'); - fullName.Prepend("\""); - fullName << "\" " << email; - fEmailList.AddChoice(fullName.String()); + +const char* +THeaderView::To() const +{ + return fToControl->Text(); +} + + +void +THeaderView::SetTo(const char* to) +{ + fToControl->SetText(to); +} + + +bool +THeaderView::IsCcEmpty() const +{ + return Cc() == NULL || Cc()[0] == '\0'; +} + + +const char* +THeaderView::Cc() const +{ + return fCcControl != NULL ? fCcControl->Text() : NULL; +} + + +void +THeaderView::SetCc(const char* cc) +{ + fCcControl->SetText(cc); + + if (fIncoming) { + if (cc != NULL && cc[0] != '\0') { + if (fCcControl->IsHidden(this)) { + fCcControl->Show(); + fCcLabel->Show(); } + } else if (!fCcControl->IsHidden(this)) { + fCcControl->Hide(); + fCcLabel->Hide(); + } + } +} - // support for 3rd-party People apps. Looks like a job for - // multiple keyword (so you can have several e-mail addresses in - // one attribute, perhaps comma separated) indices! Which aren't - // yet in BFS. - for (int16 i = 2; i < 6; i++) { - char attr[16]; - sprintf(attr, "META:email%d", i); - if (file.ReadAttrString(attr, &email) >= B_OK) - fEmailList.AddChoice(email.String()); - } + +bool +THeaderView::IsBccEmpty() const +{ + return Bcc() == NULL || Bcc()[0] == '\0'; +} + + +const char* +THeaderView::Bcc() const +{ + return fBccControl != NULL ? fBccControl->Text() : NULL; +} + + +void +THeaderView::SetBcc(const char* bcc) +{ + if (fBccControl != NULL) + fBccControl->SetText(bcc); +} + + +bool +THeaderView::IsSubjectEmpty() const +{ + return Subject() == NULL || Subject()[0] == '\0'; +} + + +const char* +THeaderView::Subject() const +{ + return fSubjectControl->Text(); +} + + +void +THeaderView::SetSubject(const char* subject) +{ + fSubjectControl->SetText(subject); +} + + +bool +THeaderView::IsDateEmpty() const +{ + return Date() == NULL || Date()[0] == '\0'; +} + + +const char* +THeaderView::Date() const +{ + return fDateView != NULL ? fDateView->Text() : NULL; +} + + +void +THeaderView::SetDate(const char* date) +{ + if (fDateView != NULL) + fDateView->SetText(date); +} + + +int32 +THeaderView::AccountID() const +{ + return fAccountID; +} + + +const char* +THeaderView::AccountName() const +{ + BMenuItem* menuItem = fAccountMenu->FindMarked(); + if (menuItem != NULL) + return menuItem->Label(); + + return NULL; +} + + +void +THeaderView::SetAccount(int32 id) +{ + fAccountID = id; + + for (int32 i = fAccountMenu->CountItems(); i-- > 0;) { + BMenuItem* item = fAccountMenu->ItemAt(i); + if (item == NULL) + continue; + + BMessage* message = item->Message(); + if (message->GetInt32("id", -1) == id) { + item->SetMarked(true); + break; } } } void -THeaderView::InitGroupCompletion() +THeaderView::SetAccount(const char* name) { - // get boot volume - BVolume volume; - BVolumeRoster().GetBootVolume(&volume); - - // Build a list of all unique groups and the addresses they expand to. - BQuery query; - query.SetVolume(&volume); - query.SetPredicate("META:group=**"); - query.Fetch(); - - map groupMap; - entry_ref ref; - BNode file; - while (query.GetNextRef(&ref) == B_OK) { - if (file.SetTo(&ref) != B_OK) - continue; - - BString groups; - if (file.ReadAttrString("META:group", &groups) < B_OK || groups.Length() == 0) - continue; - - BString address; - file.ReadAttrString("META:email", &address); - - // avoid adding an empty address - if (address.Length() == 0) - continue; - - char *group = groups.LockBuffer(groups.Length()); - char *next = strchr(group, ','); - - for (;;) { - if (next) - *next = 0; - - while (*group && *group == ' ') - group++; - - BString *groupString = new BString(group); - BString *addressListString = NULL; - - // nobody is in this group yet, start it off - if (groupMap[groupString] == NULL) { - addressListString = new BString(*groupString); - addressListString->Append(" "); - groupMap[groupString] = addressListString; - } else { - addressListString = groupMap[groupString]; - addressListString->Append(", "); - delete groupString; - } - - // Append the user's address to the end of the string with the - // comma separated list of addresses. If not present, add the - // < and > brackets around the address. - - if (address.FindFirst ('<') < 0) { - address.ReplaceAll ('>', '_'); - address.Prepend ("<"); - address.Append(">"); - } - addressListString->Append(address); - - if (!next) - break; - - group = next + 1; - next = strchr(group, ','); - } + BMenuItem* item = fAccountMenu->FindItem(name); + if (item != NULL) { + item->SetMarked(true); + fAccountID = item->Message()->GetInt32("id", -1); } +} - map::iterator iter; - for (iter = groupMap.begin(); iter != groupMap.end();) { - BString *group = iter->first; - BString *addr = iter->second; - fEmailList.AddChoice(addr->String()); - ++iter; - groupMap.erase(group); - delete group; - delete addr; - } + +status_t +THeaderView::SetFromMessage(BEmailMessage* mail) +{ + // Set Subject:, From:, To: & Cc: fields + SetSubject(mail->Subject()); + SetFrom(mail->From()); + SetTo(mail->To()); + SetCc(mail->CC()); + + BString accountName; + if (fAccount != NULL && mail->GetAccountName(accountName) == B_OK) + SetAccount(accountName); + + // Set the date on this message + const char* dateField = mail->Date(); + SetDate(dateField != NULL ? dateField : B_TRANSLATE("Unknown")); + + return B_OK; } @@ -662,8 +513,8 @@ THeaderView::MessageReceived(BMessage *msg) switch (msg->what) { case B_SIMPLE_DATA: { - BTextView *textView = dynamic_cast(Window()->CurrentFocus()); - if (dynamic_cast(textView->Parent()) != NULL) + BTextView* textView = dynamic_cast(Window()->CurrentFocus()); + if (dynamic_cast(textView->Parent()) != NULL) textView->Parent()->MessageReceived(msg); else { BMessage message(*msg); @@ -682,34 +533,12 @@ THeaderView::MessageReceived(BMessage *msg) int32 account; if (msg->FindInt32("id",(int32 *)&account) >= B_OK) fAccountID = account; - + BMessage message(FIELD_CHANGED); // field doesn't matter; no special processing for this field // it's just to turn on the save button message.AddInt32("bitmask", 0); Window()->PostMessage(&message, Window()); - - break; - } - - case kMsgEncoding: - { - BMessage message(*msg); - int32 charSet; - - if (msg->FindInt32("charset", &charSet) == B_OK) - fCharacterSetUserSees = charSet; - - message.what = CHARSET_CHOICE_MADE; - message.AddInt32 ("charset", fCharacterSetUserSees); - Window()->PostMessage (&message, Window()); - - BMessage message2(FIELD_CHANGED); - // field doesn't matter; no special processing for this field - // it's just to turn on the save button - message2.AddInt32("bitmask", 0); - Window()->PostMessage(&message2, Window()); - break; } } @@ -719,531 +548,15 @@ THeaderView::MessageReceived(BMessage *msg) void THeaderView::AttachedToWindow() { - if (fToMenu) { - fToMenu->SetTargetForItems(fTo); - fToMenu->SetPredicate("META:email=**"); - } - if (fCcMenu) { - fCcMenu->SetTargetForItems(fCc); - fCcMenu->SetPredicate("META:email=**"); - } - if (fBccMenu) { - fBccMenu->SetTargetForItems(fBcc); - fBccMenu->SetPredicate("META:email=**"); - } - if (fTo) - fTo->SetTarget(Looper()); - if (fSubject) - fSubject->SetTarget(Looper()); - if (fCc) - fCc->SetTarget(Looper()); - if (fBcc) - fBcc->SetTarget(Looper()); - if (fAccount) + fToControl->SetTarget(Looper()); + fSubjectControl->SetTarget(Looper()); + fCcControl->SetTarget(Looper()); + if (fBccControl != NULL) + fBccControl->SetTarget(Looper()); + if (fAccount != NULL) fAccount->SetTarget(Looper()); - if (fAccountMenu) + if (fAccountMenu != NULL) fAccountMenu->SetTargetForItems(this); - if (fEncodingMenu) - fEncodingMenu->SetTargetForItems(this); - BBox::AttachedToWindow(); + BView::AttachedToWindow(); } - - -status_t -THeaderView::LoadMessage(BEmailMessage *mail) -{ - // Set the date on this message - const char *dateField = mail->Date(); - char string[256]; - sprintf(string, "%s", dateField != NULL ? dateField : "Unknown"); - fDate->SetText(string); - - // Set contents of header fields - if (fIncoming && !fResending) { - if (fBcc != NULL) - fBcc->SetEnabled(false); - - if (fCc != NULL) { - fCc->SetEnabled(false); - fCc->SetText(mail->CC()); - } - - if (fAccount != NULL) - fAccount->SetEnabled(false); - - if (fAccountTo != NULL) - fAccountTo->SetEnabled(false); - - fSubject->SetEnabled(false); - fTo->SetEnabled(false); - - // show/hide CC field - bool haveText = false; - if (mail->CC() != NULL && strlen(mail->CC()) > 0) { - haveText = true; - } - bool isHidden = fCc->IsHidden(this); // hidden relative to parent - if (haveText && isHidden) { - float diff = fAccountTo->Frame().top - fTo->Frame().top; - fSubject->MoveBy(0, diff); - fDate->MoveBy(0, diff); - fDateLabel->MoveBy(0, diff); - fCc->Show(); - this->ResizeBy(0, diff); - } - else if (!haveText && !isHidden) { - float diff = fAccountTo->Frame().top - fTo->Frame().top; - fSubject->MoveBy(0, - diff); - fDate->MoveBy(0, - diff); - fDateLabel->MoveBy(0, - diff); - fCc->Hide(); - this->ResizeBy(0, - diff); - } - } - - // Set Subject: & From: fields - fSubject->SetText(mail->Subject()); - fTo->SetText(mail->From()); - - // Set Account/To Field - if (fAccountTo != NULL) - fAccountTo->SetText(mail->To()); - - BString accountName; - if (fAccount != NULL && mail->GetAccountName(accountName) == B_OK) - fAccount->SetText(accountName); - - return B_OK; -} - - -// #pragma mark - TTextControl - - -TTextControl::TTextControl(BRect rect, const char *label, BMessage *msg, - bool incoming, bool resending, int32 resizingMode) - : BComboBox(rect, "happy", label, msg, resizingMode), - fRefDropMenu(NULL) - //:BTextControl(rect, "happy", label, "", msg, resizingMode) -{ - strcpy(fLabel, label); - fCommand = msg != NULL ? msg->what : 0UL; - fIncoming = incoming; - fResending = resending; -} - - -void -TTextControl::AttachedToWindow() -{ - SetHighColor(0, 0, 0); - // BTextControl::AttachedToWindow(); - BComboBox::AttachedToWindow(); - - SetDivider(Divider() + kTextControlDividerOffset); -} - - -void -TTextControl::MessageReceived(BMessage *msg) -{ - switch (msg->what) { - case B_SIMPLE_DATA: { - if (fIncoming && !fResending) - return; - - int32 buttons = -1; - BPoint point; - if (msg->FindInt32("buttons", &buttons) != B_OK) - buttons = B_PRIMARY_MOUSE_BUTTON; - - if (buttons != B_PRIMARY_MOUSE_BUTTON - && msg->FindPoint("_drop_point_", &point) != B_OK) - return; - - BMessage message(REFS_RECEIVED); - bool enclosure = false; - BString addressList; - // Batch up the addresses to be added, since we can only - // insert a few times before deadlocking since inserting - // sends a notification message to the window BLooper, - // which is busy doing this insert. BeOS message queues - // are annoyingly limited in their design. - - entry_ref ref; - for (int32 index = 0;msg->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 (fCommand != SUBJECT_FIELD - && !strcmp(type,"application/x-person")) { - // add person's E-mail address to the To: field - - BString attr = ""; - if (buttons == B_PRIMARY_MOUSE_BUTTON) { - if (msg->FindString("attr", &attr) < B_OK) - attr = "META:email"; // If not META:email3 etc. - } 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(kMsgAddressChosen); - itemMsg->AddString("address", address.String()); - itemMsg->AddRef("ref", &ref); - - 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 << ">"; - } - - if (addressList.Length() > 0) - addressList << ", "; - addressList << address; - } - } else { - enclosure = true; - message.AddRef("refs", &ref); - } - } - } - - if (addressList.Length() > 0) { - BTextView *textView = TextView(); - int end = textView->TextLength(); - if (end != 0) { - textView->Select(end, end); - textView->Insert(", "); - } - textView->Insert(addressList.String()); - } - - if (enclosure) - Window()->PostMessage(&message, Window()); - break; - } - - case M_SELECT: - { - BTextView *textView = (BTextView *)ChildAt(0); - if (textView != NULL) - textView->Select(0, textView->TextLength()); - break; - } - - case kMsgAddressChosen: { - BString display; - BString address; - entry_ref ref; - - if (msg->FindString("address", &address) != B_OK - || msg->FindRef("ref", &ref) != B_OK) - return; - - if (address.Length() > 0) { - BString name; - BNode node(&ref); - - display = address; - - node.ReadAttrString("META:name", &name); - if (name.Length() > 0) { - display = ""; - display << "\"" << name << "\" <" << address << ">"; - } - - BTextView *textView = TextView(); - int end = textView->TextLength(); - if (end != 0) { - textView->Select(end, end); - textView->Insert(", "); - } - textView->Insert(display.String()); - } - break; - } - - default: - // BTextControl::MessageReceived(msg); - BComboBox::MessageReceived(msg); - } -} - - -bool -TTextControl::HasFocus() -{ - return TextView()->IsFocus(); -} - - -// #pragma mark - QPopupMenu - - -QPopupMenu::QPopupMenu(const char *title) - : QueryMenu(title, true), - fGroups(0) -{ -} - - -void -QPopupMenu::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); - } - - // The target (a TTextControl) will examine all the People files specified - // and add the emails and names to the string it is displaying (same code - // is used for drag and drop of People files). - BMessage *msg = new BMessage(B_SIMPLE_DATA); - msg->AddRef("refs", ref); - msg->AddInt64("node", node); - if (attr) // For nonstandard e-mail attributes, like META:email3 - msg->AddString("attr", attr); - msg->AddString("sortkey", sortKey); - - BMenuItem *newItem = new BMenuItem(label.String(), msg); - if (fTargetHandler) - newItem->SetTarget(fTargetHandler); - - // If no group, just add it to ourself; else add it to group menu - BMenu *parentMenu = groupMenu ? groupMenu : this; - if (groupMenu) { - // Add ref to group super item. - BMessage *superMsg = superItem->Message(); - superMsg->AddRef("refs", ref); - } - - // Add it to the appropriate menu. Use alphabetical order by sortKey to - // insert it in the right spot (a dumb linear search so this will be slow). - // Start searching from the end of the menu, since the main menu includes - // all the groups at the top and we don't want to mix it in with them. - // Thus the search starts at the bottom and ends when we hit a separator - // line or the top of the menu. - - int32 index = parentMenu->CountItems(); - while (index-- > 0) { - BMenuItem *item = parentMenu->ItemAt(index); - if (item == NULL || dynamic_cast(item) != NULL) - break; - - BMessage *message = item->Message(); - BString key; - - // Stop when testKey < sortKey. - if (message != NULL - && message->FindString("sortkey", &key) == B_OK - && ICompare(key, sortKey) < 0) - break; - } - - if (!parentMenu->AddItem(newItem, index + 1)) { - fprintf (stderr, "QPopupMenu::AddPersonItem: Unable to add menu " - "item \"%s\" at index %" B_PRId32 ".\n", sortKey.String(), index + 1); - delete newItem; - } -} - - -void -QPopupMenu::EntryCreated(const entry_ref &ref, ino_t node) -{ - BNode file; - if (file.SetTo(&ref) < B_OK) - return; - - // Make sure the pop-up menu is ready for additions. Need a bunch of - // groups at the top, a divider line, and miscellaneous people added below - // the line. - - int32 items = CountItems(); - if (!items) - AddSeparatorItem(); - - // Does the file have a group attribute? OK to have none. - BString groups; - const char *kNoGroup = "NoGroup!"; - file.ReadAttrString("META:group", &groups); - if (groups.Length() <= 0) - groups = kNoGroup; - - // Add the e-mail address to the all people group. Then add it to all the - // group menus that it exists in (based on the comma separated list of - // groups from the People file), optionally making the group menu if it - // doesn't exist. If it's in the special NoGroup! list, then add it below - // the groups. - - bool allPeopleGroupDone = false; - BMenu *groupMenu; - do { - BString group; - - if (!allPeopleGroupDone) { - // Create the default group for all people, if it doesn't exist yet. - group = "All People"; - allPeopleGroupDone = true; - } else { - // Break out the next group from the comma separated string. - int32 comma; - if ((comma = groups.FindFirst(',')) > 0) { - groups.MoveInto(group, 0, comma); - groups.Remove(0, 1); - } else - group.Adopt(groups); - } - - // trim white spaces - int32 i = 0; - for (i = 0; isspace(group.ByteAt(i)); i++) {} - if (i) - group.Remove(0, i); - for (i = group.Length() - 1; isspace(group.ByteAt(i)); i--) {} - group.Truncate(i + 1); - - groupMenu = NULL; - BMenuItem *superItem = NULL; // Corresponding item for group menu. - - if (group.Length() > 0 && group != kNoGroup) { - BMenu *sub; - - // Look for submenu with label == group name - for (int32 i = 0; i < items; i++) { - if ((sub = SubmenuAt(i)) != NULL) { - superItem = sub->Superitem(); - if (!strcmp(superItem->Label(), group.String())) { - groupMenu = sub; - i++; - break; - } - } - } - - // If no submenu, create one - if (!groupMenu) { - // Find where it should go (alphabetical) - int32 mindex = 0; - for (; mindex < fGroups; mindex++) { - if (strcmp(ItemAt(mindex)->Label(), group.String()) > 0) - break; - } - - groupMenu = new BMenu(group.String()); - groupMenu->SetFont(be_plain_font); - AddItem(groupMenu, mindex); - - superItem = groupMenu->Superitem(); - superItem->SetMessage(new BMessage(B_SIMPLE_DATA)); - if (fTargetHandler) - superItem->SetTarget(fTargetHandler); - - fGroups++; - } - } - - BString name; - file.ReadAttrString("META:name", &name); - - BString email; - file.ReadAttrString("META:email", &email); - - if (email.Length() != 0 || name.Length() != 0) - AddPersonItem(&ref, node, name, email, NULL, groupMenu, superItem); - - // support for 3rd-party People apps - for (int16 i = 2; i < 6; i++) { - char attr[16]; - sprintf(attr, "META:email%d", i); - if (file.ReadAttrString(attr, &email) >= B_OK && email.Length() > 0) - AddPersonItem(&ref, node, name, email, attr, groupMenu, superItem); - } - } while (groups.Length() > 0); -} - - -void -QPopupMenu::EntryRemoved(ino_t /*node*/) -{ -} - diff --git a/src/apps/mail/Header.h b/src/apps/mail/Header.h index 2f5d2f8b50..c9ff2a0e8e 100644 --- a/src/apps/mail/Header.h +++ b/src/apps/mail/Header.h @@ -35,103 +35,77 @@ All rights reserved. #define _HEADER_H -#include "ComboBox.h" - -#include +#include #include -#include -#include #include -#include -#include -#include + +#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 */ diff --git a/src/apps/mail/Jamfile b/src/apps/mail/Jamfile index 468997df73..202f9afb9a 100644 --- a/src/apps/mail/Jamfile +++ b/src/apps/mail/Jamfile @@ -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 diff --git a/src/apps/mail/MailApp.cpp b/src/apps/mail/MailApp.cpp index 328953d482..c17ba88004 100644 --- a/src/apps/mail/MailApp.cpp +++ b/src/apps/mail/MailApp.cpp @@ -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; diff --git a/src/apps/mail/MailApp.h b/src/apps/mail/MailApp.h index 86d675ee9a..ad0d2f9860 100644 --- a/src/apps/mail/MailApp.h +++ b/src/apps/mail/MailApp.h @@ -34,6 +34,7 @@ All rights reserved. #ifndef _MAIL_APP_H #define _MAIL_APP_H + #include #include #include @@ -41,6 +42,9 @@ All rights reserved. #include #include +#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; }; diff --git a/src/apps/mail/MailSupport.cpp b/src/apps/mail/MailSupport.cpp index 71721dcc4a..55760a2483 100644 --- a/src/apps/mail/MailSupport.cpp +++ b/src/apps/mail/MailSupport.cpp @@ -166,4 +166,3 @@ add_query_menu_items(BMenu* menu, const char* attribute, uint32 what, return index; } - diff --git a/src/apps/mail/MailWindow.cpp b/src/apps/mail/MailWindow.cpp index 5453023210..b11711c2f1 100644 --- a/src/apps/mail/MailWindow.cpp +++ b/src/apps/mail/MailWindow.cpp @@ -43,22 +43,30 @@ 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 +#include #include #include @@ -169,7 +177,7 @@ TMailWindow::TMailWindow(BRect rect, const char* title, TMailApp* app, const entry_ref* ref, const char* to, const BFont* font, bool resending, BMessenger* messenger) : - BWindow(rect, title, B_DOCUMENT_WINDOW, 0), + BWindow(rect, title, B_DOCUMENT_WINDOW, B_AUTO_UPDATE_SIZE_LIMITS), fApp(app), fMail(NULL), @@ -177,6 +185,7 @@ TMailWindow::TMailWindow(BRect rect, const char* title, TMailApp* app, fFieldState(0), fPanel(NULL), fLeaveStatusMenu(NULL), + fEncodingMenu(NULL), fZoom(rect), fEnclosuresView(NULL), fPrevTrackerPositionSaved(false), @@ -196,7 +205,6 @@ TMailWindow::TMailWindow(BRect rect, const char* title, TMailApp* app, if (messenger != NULL) fTrackerMessenger = *messenger; - float height; BMenu* menu; BMenu* subMenu; BMenuItem* item; @@ -429,24 +437,6 @@ TMailWindow::TMailWindow(BRect rect, const char* title, TMailApp* app, menu->AddSeparatorItem(); fSaveAddrMenu = subMenu = new BMenu(B_TRANSLATE("Save address")); menu->AddItem(subMenu); - fMenuBar->AddItem(menu); - - // Spam Menu - - if (fApp->ShowSpamGUI()) { - menu = new BMenu("Spam filtering"); - menu->AddItem(new BMenuItem("Mark as spam and move to trash", - new BMessage(M_TRAIN_SPAM_AND_DELETE), 'K')); - menu->AddItem(new BMenuItem("Mark as spam", - new BMessage(M_TRAIN_SPAM), 'K', B_OPTION_KEY)); - menu->AddSeparatorItem(); - menu->AddItem(new BMenuItem("Unmark this message", - new BMessage(M_UNTRAIN))); - menu->AddSeparatorItem(); - menu->AddItem(new BMenuItem("Mark as genuine", - new BMessage(M_TRAIN_GENUINE), 'K', B_SHIFT_KEY)); - fMenuBar->AddItem(menu); - } } else { menu->AddItem(fSendNow = new BMenuItem(B_TRANSLATE("Send message"), new BMessage(M_SEND_NOW), 'M')); @@ -468,6 +458,80 @@ TMailWindow::TMailWindow(BRect rect, const char* title, TMailApp* app, B_TRANSLATE("Remove enclosure"), new BMessage(M_REMOVE), 'T')); } + } + + // Encoding menu + + fEncodingMenu = new BMenu(B_TRANSLATE("Encoding")); + + BMenuItem* automaticItem = NULL; + if (!resending && fIncoming) { + // Reading a message, display the Automatic item + msg = new BMessage(CHARSET_CHOICE_MADE); + msg->AddInt32("charset", B_MAIL_NULL_CONVERSION); + automaticItem = new BMenuItem(B_TRANSLATE("Automatic"), msg); + fEncodingMenu->AddItem(automaticItem); + fEncodingMenu->AddSeparatorItem(); + } + + uint32 defaultCharSet = resending || !fIncoming + ? fApp->MailCharacterSet() : B_MAIL_NULL_CONVERSION; + bool markedCharSet = false; + + BCharacterSetRoster roster; + BCharacterSet charSet; + while (roster.GetNextCharacterSet(&charSet) == B_OK) { + BString name(charSet.GetPrintName()); + const char* mime = charSet.GetMIMEName(); + if (mime != NULL) + name << " (" << mime << ")"; + + uint32 convertID; + if (mime == NULL || strcasecmp(mime, "UTF-8") != 0) + convertID = charSet.GetConversionID(); + else + convertID = B_MAIL_UTF8_CONVERSION; + + msg = new BMessage(CHARSET_CHOICE_MADE); + msg->AddInt32("charset", convertID); + fEncodingMenu->AddItem(item = new BMenuItem(name.String(), msg)); + if (convertID == defaultCharSet && !markedCharSet) { + item->SetMarked(true); + markedCharSet = true; + } + } + + msg = new BMessage(CHARSET_CHOICE_MADE); + msg->AddInt32("charset", B_MAIL_US_ASCII_CONVERSION); + fEncodingMenu->AddItem(item = new BMenuItem("US-ASCII", msg)); + if (defaultCharSet == B_MAIL_US_ASCII_CONVERSION && !markedCharSet) { + item->SetMarked(true); + markedCharSet = true; + } + + if (automaticItem != NULL && !markedCharSet) + automaticItem->SetMarked(true); + + menu->AddSeparatorItem(); + menu->AddItem(fEncodingMenu); + fMenuBar->AddItem(menu); + fEncodingMenu->SetRadioMode(true); + fEncodingMenu->SetTargetForItems(this); + + // Spam Menu + + if (!resending && fIncoming && fApp->ShowSpamGUI()) { + menu = new BMenu("Spam filtering"); + menu->AddItem(new BMenuItem("Mark as spam and move to trash", + new BMessage(M_TRAIN_SPAM_AND_DELETE), 'K')); + menu->AddItem(new BMenuItem("Mark as spam", + new BMessage(M_TRAIN_SPAM), 'K', B_OPTION_KEY)); + menu->AddSeparatorItem(); + menu->AddItem(new BMenuItem("Unmark this message", + new BMessage(M_UNTRAIN))); + menu->AddSeparatorItem(); + menu->AddItem(new BMenuItem("Mark as genuine", + new BMessage(M_TRAIN_GENUINE), 'K', B_SHIFT_KEY)); fMenuBar->AddItem(menu); } @@ -478,49 +542,28 @@ TMailWindow::TMailWindow(BRect rect, const char* title, TMailApp* app, _RebuildQueryMenu(true); - // Menu Bar - - AddChild(fMenuBar); - height = fMenuBar->Bounds().bottom + 1; - // Button Bar BuildToolBar(); - float bbheight = 0; + if (!fApp->ShowToolBar()) + fToolBar->Hide(); - bool showToolBar = fApp->ShowToolBar(); - - if (showToolBar) { - bbheight = fToolBar->MinSize().height; - fToolBar->ResizeTo(Bounds().right, bbheight); - fToolBar->MoveTo(0, height); - fToolBar->Show(); - } - - r.top = r.bottom = height + bbheight + 1; - fHeaderView = new THeaderView (r, rect, fIncoming, resending, - (resending || !fIncoming) - ? fApp->MailCharacterSet() - // Use preferences setting for composing mail. - : B_MAIL_NULL_CONVERSION, - // Default is automatic selection for reading mail. + fHeaderView = new THeaderView(fIncoming, resending, fApp->DefaultAccount()); - r = Frame(); - r.OffsetTo(0, 0); - r.top = fHeaderView->Frame().bottom - 1; - fContentView = new TContentView(r, fIncoming, const_cast(font), + fContentView = new TContentView(fIncoming, const_cast(font), false, fApp->ColoredQuotes()); // TContentView needs to be properly const, for now cast away constness - AddChild(fHeaderView); - if (fEnclosuresView) - AddChild(fEnclosuresView); - AddChild(fContentView); + BLayoutBuilder::Group<>(this, B_VERTICAL, 0) + .Add(fMenuBar) + .Add(fToolBar) + .Add(fHeaderView) + .Add(fContentView); - if (to) - fHeaderView->fTo->SetText(to); + if (to != NULL) + fHeaderView->SetTo(to); AddShortcut('n', B_COMMAND_KEY, new BMessage(M_NEW)); @@ -564,9 +607,7 @@ TMailWindow::TMailWindow(BRect rect, const char* title, TMailApp* app, } } - OpenMessage(ref, fHeaderView->fCharacterSetUserSees); - - _UpdateSizeLimits(); + OpenMessage(ref, _CurrentCharacterSet()); AddShortcut('q', B_SHIFT_KEY, new BMessage(kMsgQuitAndKeepAllStatus)); } @@ -664,50 +705,20 @@ TMailWindow::BuildToolBar() _AddReadButton(); } fToolBar->AddGlue(); - - fToolBar->Hide(); - AddChild(fToolBar); } void TMailWindow::UpdateViews() { - float bbheight = 0; - float nextY = fMenuBar->Frame().bottom + 1; - uint8 showToolBar = fApp->ShowToolBar(); // Show/Hide Button Bar if (showToolBar) { - // Create the Button Bar if needed - if (!fToolBar) - BuildToolBar(); - - bbheight = fToolBar->MinSize().height; - fToolBar->ResizeTo(Bounds().right, bbheight); - fToolBar->MoveTo(0, nextY); - nextY += bbheight + 1; if (fToolBar->IsHidden()) fToolBar->Show(); - else - fToolBar->Invalidate(); - } else if (fToolBar && !fToolBar->IsHidden()) + } else if (!fToolBar->IsHidden()) fToolBar->Hide(); - - // Arange other views to match - fHeaderView->MoveTo(0, nextY); - nextY = fHeaderView->Frame().bottom; - if (fEnclosuresView) { - fEnclosuresView->MoveTo(0, nextY); - nextY = fEnclosuresView->Frame().bottom + 1; - } - BRect bounds(Bounds()); - fContentView->MoveTo(0, nextY - 1); - fContentView->ResizeTo(bounds.right - bounds.left, - bounds.bottom - nextY + 1); - - _UpdateSizeLimits(); } @@ -839,7 +850,7 @@ TMailWindow::SetTrackerSelectionToCurrent() void TMailWindow::PreserveReadingPos(bool save) { - BScrollBar* scroll = fContentView->fTextView->ScrollBar(B_VERTICAL); + BScrollBar* scroll = fContentView->TextView()->ScrollBar(B_VERTICAL); if (scroll == NULL || fRef == NULL) return; @@ -893,17 +904,15 @@ TMailWindow::FrameResized(float width, float height) void TMailWindow::MenusBeginning() { - bool enable; int32 finish = 0; int32 start = 0; - BTextView* textView; if (!fIncoming) { - bool gotToField = fHeaderView->fTo->Text()[0] != 0; - bool gotCcField = fHeaderView->fCc->Text()[0] != 0; - bool gotBccField = fHeaderView->fBcc->Text()[0] != 0; - bool gotSubjectField = fHeaderView->fSubject->Text()[0] != 0; - bool gotText = fContentView->fTextView->Text()[0] != 0; + bool gotToField = !fHeaderView->IsToEmpty(); + bool gotCcField = !fHeaderView->IsCcEmpty(); + bool gotBccField = !fHeaderView->IsBccEmpty(); + bool gotSubjectField = !fHeaderView->IsSubjectEmpty(); + bool gotText = fContentView->TextView()->Text()[0] != 0; fSendNow->SetEnabled(gotToField || gotBccField); fSendLater->SetEnabled(fChanged && (gotToField || gotCcField || gotBccField || gotSubjectField || gotText)); @@ -922,13 +931,12 @@ TMailWindow::MenusBeginning() && fEnclosuresView->fList->CurrentSelection() >= 0); } else { if (fResending) { - enable = strlen(fHeaderView->fTo->Text()); + bool enable = !fHeaderView->IsToEmpty(); fSendNow->SetEnabled(enable); - // fSendLater->SetEnabled(enable); + //fSendLater->SetEnabled(enable); - if (fHeaderView->fTo->HasFocus()) { - textView = fHeaderView->fTo->TextView(); - textView->GetSelection(&start, &finish); + if (fHeaderView->ToControl()->HasFocus()) { + fHeaderView->ToControl()->GetSelection(&start, &finish); fCut->SetEnabled(start != finish); be_clipboard->Lock(); @@ -950,15 +958,16 @@ TMailWindow::MenusBeginning() } } - fPrint->SetEnabled(fContentView->fTextView->TextLength()); + fPrint->SetEnabled(fContentView->TextView()->TextLength()); - textView = dynamic_cast(CurrentFocus()); + BTextView* textView = dynamic_cast(CurrentFocus()); if (textView != NULL - && dynamic_cast(textView->Parent()) != NULL) { + && (dynamic_cast(textView->Parent()) != NULL + || dynamic_cast(textView->Parent()) != NULL)) { // one of To:, Subject:, Account:, Cc:, Bcc: textView->GetSelection(&start, &finish); - } else if (fContentView->fTextView->IsFocus()) { - fContentView->fTextView->GetSelection(&start, &finish); + } else if (fContentView->TextView()->IsFocus()) { + fContentView->TextView()->GetSelection(&start, &finish); if (!fIncoming) { fQuote->SetEnabled(true); fRemoveQuote->SetEnabled(true); @@ -1019,7 +1028,7 @@ TMailWindow::MessageReceived(BMessage* msg) break; // reload the current message - OpenMessage(&ref, fHeaderView->fCharacterSetUserSees); + OpenMessage(&ref, _CurrentCharacterSet()); break; } @@ -1035,7 +1044,7 @@ TMailWindow::MessageReceived(BMessage* msg) if (fieldMask == FIELD_BODY) length = ((TTextView*)source)->TextLength(); else - length = ((BComboBox*)source)->TextView()->TextLength(); + length = ((AddressTextControl*)source)->TextLength(); if (length) fFieldState |= fieldMask; @@ -1056,10 +1065,10 @@ TMailWindow::MessageReceived(BMessage* msg) // Update title bar if "subject" has changed if (!fIncoming && (fieldMask & FIELD_SUBJECT) != 0) { // If no subject, set to "Mail" - if (!fHeaderView->fSubject->TextView()->TextLength()) + if (fHeaderView->IsSubjectEmpty()) SetTitle(B_TRANSLATE_SYSTEM_NAME("Mail")); else - SetTitle(fHeaderView->fSubject->Text()); + SetTitle(fHeaderView->Subject()); } break; } @@ -1251,7 +1260,7 @@ TMailWindow::MessageReceived(BMessage* msg) TMailWindow* window = static_cast(be_app)->FindWindow(nextRef); if (window == NULL) - OpenMessage(&nextRef, fHeaderView->fCharacterSetUserSees); + OpenMessage(&nextRef, _CurrentCharacterSet()); else window->Activate(); @@ -1324,7 +1333,7 @@ TMailWindow::MessageReceived(BMessage* msg) BMessage message(M_HEADER); message.AddBool("header", showHeader); - PostMessage(&message, fContentView->fTextView); + PostMessage(&message, fContentView->TextView()); break; } case M_RAW: @@ -1333,7 +1342,7 @@ TMailWindow::MessageReceived(BMessage* msg) fRaw->SetMarked(raw); BMessage message(M_RAW); message.AddBool("raw", raw); - PostMessage(&message, fContentView->fTextView); + PostMessage(&message, fContentView->TextView()); break; } case M_SEND_NOW: @@ -1519,6 +1528,16 @@ TMailWindow::MessageReceived(BMessage* msg) break; case CHARSET_CHOICE_MADE: + { + int32 charSet; + if (msg->FindInt32("charset", &charSet) != B_OK) + break; + + BMessage update(FIELD_CHANGED); + update.AddInt32("bitmask", 0); + // just enable the save button + PostMessage(&update); + if (fIncoming && !fResending) { // The user wants to see the message they are reading (not // composing) displayed with a different kind of character set @@ -1527,11 +1546,10 @@ TMailWindow::MessageReceived(BMessage* msg) // retrieved from the header view when it is needed. entry_ref fileRef = *fRef; - int32 characterSet; - msg->FindInt32("charset", &characterSet); - OpenMessage(&fileRef, characterSet); + OpenMessage(&fileRef, charSet); } break; + } case REFS_RECEIVED: AddEnclosure(msg); @@ -1569,8 +1587,7 @@ TMailWindow::MessageReceived(BMessage* msg) else if (currentFlag != B_READ && !wasReadMsg) MarkMessageRead(fRef, B_SEEN); - OpenMessage(&nextRef, - fHeaderView->fCharacterSetUserSees); + OpenMessage(&nextRef, _CurrentCharacterSet()); } else { window->Activate(); //fSent = true; @@ -1597,15 +1614,15 @@ TMailWindow::MessageReceived(BMessage* msg) case RESET_BUTTONS: fChanged = false; fFieldState = 0; - if (fHeaderView->fTo->TextView()->TextLength()) + if (!fHeaderView->IsToEmpty()) fFieldState |= FIELD_TO; - if (fHeaderView->fSubject->TextView()->TextLength()) + if (!fHeaderView->IsSubjectEmpty()) fFieldState |= FIELD_SUBJECT; - if (fHeaderView->fCc->TextView()->TextLength()) + if (!fHeaderView->IsCcEmpty()) fFieldState |= FIELD_CC; - if (fHeaderView->fBcc->TextView()->TextLength()) + if (!fHeaderView->IsBccEmpty()) fFieldState |= FIELD_BCC; - if (fContentView->fTextView->TextLength()) + if (fContentView->TextView()->TextLength() != 0) fFieldState |= FIELD_BODY; fToolBar->SetActionEnabled(M_SAVE_AS_DRAFT, false); @@ -1628,7 +1645,7 @@ TMailWindow::MessageReceived(BMessage* msg) alert->Go(); } else { fSpelling->SetMarked(!fSpelling->IsMarked()); - fContentView->fTextView->EnableSpellCheck( + fContentView->TextView()->EnableSpellCheck( fSpelling->IsMarked()); } break; @@ -1724,12 +1741,12 @@ TMailWindow::QuitRequested() int32 result; if ((!fIncoming || (fIncoming && fResending)) && fChanged && !fSent - && (strlen(fHeaderView->fTo->Text()) - || strlen(fHeaderView->fSubject->Text()) - || (fHeaderView->fCc && strlen(fHeaderView->fCc->Text())) - || (fHeaderView->fBcc && strlen(fHeaderView->fBcc->Text())) - || (fContentView->fTextView - && strlen(fContentView->fTextView->Text())) + && (!fHeaderView->IsToEmpty() + || !fHeaderView->IsSubjectEmpty() + || !fHeaderView->IsCcEmpty() + || !fHeaderView->IsBccEmpty() + || (fContentView->TextView() != NULL + && strlen(fContentView->TextView()->Text())) || (fEnclosuresView != NULL && fEnclosuresView->fList->CountItems()))) { if (fResending) { @@ -1818,11 +1835,10 @@ TMailWindow::Show() { if (Lock()) { if (!fResending && (fIncoming || fReplying)) { - fContentView->fTextView->MakeFocus(true); + fContentView->TextView()->MakeFocus(true); } else { - BTextView* textView = fHeaderView->fTo->TextView(); - fHeaderView->fTo->MakeFocus(true); - textView->Select(0, textView->TextLength()); + fHeaderView->ToControl()->MakeFocus(true); + fHeaderView->ToControl()->SelectAll(); } Unlock(); } @@ -1838,16 +1854,16 @@ TMailWindow::Zoom(BPoint /*pos*/, float /*x*/, float /*y*/) BRect rect = Frame(); width = 80 * fApp->ContentFont().StringWidth("M") - + (rect.Width() - fContentView->fTextView->Bounds().Width() + 6); + + (rect.Width() - fContentView->TextView()->Bounds().Width() + 6); BScreen screen(this); BRect screenFrame = screen.Frame(); if (width > (screenFrame.Width() - 8)) width = screenFrame.Width() - 8; - height = max_c(fContentView->fTextView->CountLines(), 20) - * fContentView->fTextView->LineHeight(0) - + (rect.Height() - fContentView->fTextView->Bounds().Height()); + height = max_c(fContentView->TextView()->CountLines(), 20) + * fContentView->TextView()->LineHeight(0) + + (rect.Height() - fContentView->TextView()->Bounds().Height()); if (height > (screenFrame.Height() - 29)) height = screenFrame.Height() - 29; @@ -1917,22 +1933,12 @@ TMailWindow::Forward(entry_ref* ref, TMailWindow* window, if (file.InitCheck() < B_NO_ERROR) return; - fHeaderView->fSubject->SetText(fMail->Subject()); + fHeaderView->SetSubject(fMail->Subject()); // set mail account - if (useAccountFrom == ACCOUNT_FROM_MAIL) { - fHeaderView->fAccountID = fMail->Account(); - - BMenu* menu = fHeaderView->fAccountMenu; - for (int32 i = menu->CountItems(); i-- > 0;) { - BMenuItem* item = menu->ItemAt(i); - BMessage* msg; - if (item && (msg = item->Message()) != NULL - && msg->FindInt32("id") == fHeaderView->fAccountID) - item->SetMarked(true); - } - } + if (useAccountFrom == ACCOUNT_FROM_MAIL) + fHeaderView->SetAccount(fMail->Account()); if (fMail->CountComponents() > 1) { // if there are any enclosures to be added, first add the enclosures @@ -1942,7 +1948,7 @@ TMailWindow::Forward(entry_ref* ref, TMailWindow* window, fEnclosuresView->AddEnclosuresFromMail(fMail); } - fContentView->fTextView->LoadMessage(fMail, false, NULL); + fContentView->TextView()->LoadMessage(fMail, false, NULL); fChanged = false; fFieldState = 0; } @@ -1974,24 +1980,24 @@ TMailWindow::Print() B_FOLLOW_ALL_SIDES); //---------Init the header fields - #define add_header_field(field) { \ + #define add_header_field(label, field) { \ /*header_view.SetFontAndColor(be_bold_font);*/ \ - header_view.Insert(fHeaderView->field->Label()); \ + header_view.Insert(label); \ header_view.Insert(" "); \ /*header_view.SetFontAndColor(be_plain_font);*/ \ - header_view.Insert(fHeaderView->field->Text()); \ + header_view.Insert(field); \ header_view.Insert("\n"); \ } - add_header_field(fSubject); - add_header_field(fTo); - if (fHeaderView->fCc != NULL && fHeaderView->fCc->TextLength() != 0) - add_header_field(fCc); + add_header_field("Subject:", fHeaderView->Subject()); + add_header_field("To:", fHeaderView->To()); + if (!fHeaderView->IsCcEmpty()) + add_header_field(B_TRANSLATE("Cc:"), fHeaderView->Cc()); - if (fHeaderView->fDate != NULL) - header_view.Insert(fHeaderView->fDate->Text()); + if (!fHeaderView->IsDateEmpty()) + header_view.Insert(fHeaderView->Date()); - int32 maxLine = fContentView->fTextView->CountLines(); + int32 maxLine = fContentView->TextView()->CountLines(); BRect pageRect = print.PrintableRect(); BRect curPageRect = pageRect; @@ -2011,26 +2017,26 @@ TMailWindow::Print() header_height += 5; do { - int32 lineOffset = fContentView->fTextView->OffsetAt(lastLine); + int32 lineOffset = fContentView->TextView()->OffsetAt(lastLine); curPageRect.OffsetTo(0, - fContentView->fTextView->PointAt(lineOffset).y); + fContentView->TextView()->PointAt(lineOffset).y); int32 fromLine = lastLine; - lastLine = fContentView->fTextView->LineAt( + lastLine = fContentView->TextView()->LineAt( BPoint(0.0, curPageRect.bottom - ((curPage == 1) ? header_height : 0))); - float curPageHeight = fContentView->fTextView->TextHeight( + float curPageHeight = fContentView->TextView()->TextHeight( fromLine, lastLine) + (curPage == 1 ? header_height : 0); if (curPageHeight > pageRect.Height()) { - curPageHeight = fContentView->fTextView->TextHeight( + curPageHeight = fContentView->TextView()->TextHeight( fromLine, --lastLine) + (curPage == 1 ? header_height : 0); } curPageRect.bottom = curPageRect.top + curPageHeight - 1.0; if (curPage >= print.FirstPage() && curPage <= print.LastPage()) { - print.DrawView(fContentView->fTextView, curPageRect, + print.DrawView(fContentView->TextView(), curPageRect, BPoint(0.0, curPage == 1 ? header_height : 0.0)); print.SpoolPage(); } @@ -2069,18 +2075,18 @@ TMailWindow::SetTo(const char* mailTo, const char* subject, const char* ccTo, { Lock(); - if (mailTo && mailTo[0]) - fHeaderView->fTo->SetText(mailTo); - if (subject && subject[0]) - fHeaderView->fSubject->SetText(subject); - if (ccTo && ccTo[0]) - fHeaderView->fCc->SetText(ccTo); - if (bccTo && bccTo[0]) - fHeaderView->fBcc->SetText(bccTo); + if (mailTo != NULL && mailTo[0]) + fHeaderView->SetTo(mailTo); + if (subject != NULL && subject[0]) + fHeaderView->SetSubject(subject); + if (ccTo != NULL && ccTo[0]) + fHeaderView->SetCc(ccTo); + if (bccTo != NULL && bccTo[0]) + fHeaderView->SetBcc(bccTo); - if (body && body->Length()) { - fContentView->fTextView->SetText(body->String(), body->Length()); - fContentView->fTextView->GoToLine(0); + if (body != NULL && body->Length()) { + fContentView->TextView()->SetText(body->String(), body->Length()); + fContentView->TextView()->GoToLine(0); } if (enclosures && enclosures->HasRef("refs")) @@ -2096,23 +2102,20 @@ TMailWindow::CopyMessage(entry_ref* ref, TMailWindow* src) BNode file(ref); if (file.InitCheck() == B_OK) { BString string; - if (fHeaderView->fTo - && file.ReadAttrString(B_MAIL_ATTR_TO, &string) == B_OK) - fHeaderView->fTo->SetText(string.String()); + if (file.ReadAttrString(B_MAIL_ATTR_TO, &string) == B_OK) + fHeaderView->SetTo(string); - if (fHeaderView->fSubject - && file.ReadAttrString(B_MAIL_ATTR_SUBJECT, &string) == B_OK) - fHeaderView->fSubject->SetText(string.String()); + if (file.ReadAttrString(B_MAIL_ATTR_SUBJECT, &string) == B_OK) + fHeaderView->SetSubject(string); - if (fHeaderView->fCc - && file.ReadAttrString(B_MAIL_ATTR_CC, &string) == B_OK) - fHeaderView->fCc->SetText(string.String()); + if (file.ReadAttrString(B_MAIL_ATTR_CC, &string) == B_OK) + fHeaderView->SetCc(string); } - TTextView* text = src->fContentView->fTextView; + TTextView* text = src->fContentView->TextView(); text_run_array* style = text->RunArray(0, text->TextLength()); - fContentView->fTextView->SetText(text->Text(), text->TextLength(), style); + fContentView->TextView()->SetText(text->Text(), text->TextLength(), style); free(style); } @@ -2141,9 +2144,9 @@ TMailWindow::Reply(entry_ref* ref, TMailWindow* window, uint32 type) useAccountFrom == ACCOUNT_FROM_MAIL, QUOTE); // set header fields - fHeaderView->fTo->SetText(fMail->To()); - fHeaderView->fCc->SetText(fMail->CC()); - fHeaderView->fSubject->SetText(fMail->Subject()); + fHeaderView->SetTo(fMail->To()); + fHeaderView->SetCc(fMail->CC()); + fHeaderView->SetSubject(fMail->Subject()); int32 accountID; BFile file(window->fRef, B_READ_ONLY); @@ -2155,18 +2158,9 @@ TMailWindow::Reply(entry_ref* ref, TMailWindow* window, uint32 type) if ((useAccountFrom == ACCOUNT_FROM_MAIL) || (accountID > -1)) { if (useAccountFrom == ACCOUNT_FROM_MAIL) - fHeaderView->fAccountID = fMail->Account(); + fHeaderView->SetAccount(fMail->Account()); else - fHeaderView->fAccountID = accountID; - - BMenu* menu = fHeaderView->fAccountMenu; - for (int32 i = menu->CountItems(); i-- > 0;) { - BMenuItem* item = menu->ItemAt(i); - BMessage* msg; - if (item && (msg = item->Message()) != NULL - && msg->FindInt32("id") == fHeaderView->fAccountID) - item->SetMarked(true); - } + fHeaderView->SetAccount(accountID); } // create preamble string @@ -2194,44 +2188,44 @@ TMailWindow::Reply(entry_ref* ref, TMailWindow* window, uint32 type) // insert (if selection) or load (if whole mail) message text into text view int32 finish, start; - window->fContentView->fTextView->GetSelection(&start, &finish); + window->fContentView->TextView()->GetSelection(&start, &finish); if (start != finish) { char* text = (char*)malloc(finish - start + 1); if (text == NULL) return; - window->fContentView->fTextView->GetText(start, finish - start, text); + window->fContentView->TextView()->GetText(start, finish - start, text); if (text[strlen(text) - 1] != '\n') { text[strlen(text)] = '\n'; finish++; } - fContentView->fTextView->SetText(text, finish - start); + fContentView->TextView()->SetText(text, finish - start); free(text); - finish = fContentView->fTextView->CountLines(); + finish = fContentView->TextView()->CountLines(); for (int32 loop = 0; loop < finish; loop++) { - fContentView->fTextView->GoToLine(loop); - fContentView->fTextView->Insert((const char*)QUOTE); + fContentView->TextView()->GoToLine(loop); + fContentView->TextView()->Insert((const char*)QUOTE); } if (fApp->ColoredQuotes()) { - const BFont* font = fContentView->fTextView->Font(); - int32 length = fContentView->fTextView->TextLength(); + const BFont* font = fContentView->TextView()->Font(); + int32 length = fContentView->TextView()->TextLength(); TextRunArray style(length / 8 + 8); - FillInQuoteTextRuns(fContentView->fTextView, NULL, - fContentView->fTextView->Text(), length, font, &style.Array(), + FillInQuoteTextRuns(fContentView->TextView(), NULL, + fContentView->TextView()->Text(), length, font, &style.Array(), style.MaxEntries()); - fContentView->fTextView->SetRunArray(0, length, &style.Array()); + fContentView->TextView()->SetRunArray(0, length, &style.Array()); } - fContentView->fTextView->GoToLine(0); + fContentView->TextView()->GoToLine(0); if (preamble.Length() > 0) - fContentView->fTextView->Insert(preamble); + fContentView->TextView()->Insert(preamble); } else { - fContentView->fTextView->LoadMessage(mail, true, preamble); + fContentView->TextView()->LoadMessage(mail, true, preamble); } fReplying = true; @@ -2241,10 +2235,6 @@ TMailWindow::Reply(entry_ref* ref, TMailWindow* window, uint32 type) status_t TMailWindow::Send(bool now) { - uint32 characterSetToUse = fApp->MailCharacterSet(); - mail_encoding encodingForBody = quoted_printable; - mail_encoding encodingForHeaders = quoted_printable; - if (!now) { status_t status = SaveAsDraft(); if (status != B_OK) { @@ -2257,8 +2247,9 @@ TMailWindow::Send(bool now) return status; } - if (fHeaderView != NULL) - characterSetToUse = fHeaderView->fCharacterSetUserSees; + uint32 characterSetToUse = _CurrentCharacterSet(); + mail_encoding encodingForBody = quoted_printable; + mail_encoding encodingForHeaders = quoted_printable; // Set up the encoding to use for converting binary to printable ASCII. // Normally this will be quoted printable, but for some old software, @@ -2286,19 +2277,19 @@ TMailWindow::Send(bool now) // Count the number of characters in the message body which aren't in the // currently selected character set. Also see if the resulting encoded // text can safely use 7 bit characters. - if (fContentView->fTextView->TextLength() > 0) { + if (fContentView->TextView()->TextLength() > 0) { // First do a trial encoding with the user's character set. int32 converterState = 0; int32 originalLength; BString tempString; int32 tempStringLength; char* tempStringPntr; - originalLength = fContentView->fTextView->TextLength(); + originalLength = fContentView->TextView()->TextLength(); tempStringLength = originalLength * 6; // Some character sets bloat up on escape codes tempStringPntr = tempString.LockBuffer (tempStringLength); if (tempStringPntr != NULL && mail_convert_from_utf8(characterSetToUse, - fContentView->fTextView->Text(), &originalLength, + fContentView->TextView()->Text(), &originalLength, tempStringPntr, &tempStringLength, &converterState, 0x1A /* used for unknown characters */) == B_OK) { // Check for any characters which don't fit in a 7 bit encoding. @@ -2369,11 +2360,11 @@ TMailWindow::Send(bool now) result = file.InitCheck(); if (result == B_OK) { BEmailMessage mail(&file); - mail.SetTo(fHeaderView->fTo->Text(), characterSetToUse, + mail.SetTo(fHeaderView->To(), characterSetToUse, encodingForHeaders); - if (fHeaderView->fAccountID != ~0L) - mail.SendViaAccount(fHeaderView->fAccountID); + if (fHeaderView->AccountID() != ~0L) + mail.SendViaAccount(fHeaderView->AccountID()); result = mail.Send(now); } @@ -2386,13 +2377,11 @@ TMailWindow::Send(bool now) // CC field meant that it got sent out anyway, so pass in empty strings // when changing the header to force it to remove the header. - fMail->SetTo(fHeaderView->fTo->Text(), characterSetToUse, + fMail->SetTo(fHeaderView->To(), characterSetToUse, encodingForHeaders); + fMail->SetSubject(fHeaderView->Subject(), characterSetToUse, encodingForHeaders); - fMail->SetSubject(fHeaderView->fSubject->Text(), characterSetToUse, - encodingForHeaders); - fMail->SetCC(fHeaderView->fCc->Text(), characterSetToUse, - encodingForHeaders); - fMail->SetBCC(fHeaderView->fBcc->Text()); + fMail->SetCC(fHeaderView->Cc(), characterSetToUse, encodingForHeaders); + fMail->SetBCC(fHeaderView->Bcc()); //--- Add X-Mailer field { @@ -2421,7 +2410,7 @@ TMailWindow::Send(bool now) // the content text is always added to make sure there is a mail body fMail->SetBodyTextTo(""); - fContentView->fTextView->AddAsContent(fMail, fApp->WrapMode(), + fContentView->TextView()->AddAsContent(fMail, fApp->WrapMode(), characterSetToUse, encodingForBody); if (fEnclosuresView != NULL) { @@ -2440,8 +2429,8 @@ TMailWindow::Send(bool now) fMail->Attach(item->Ref(), fApp->AttachAttributes()); } } - if (fHeaderView->fAccountID != ~0L) - fMail->SendViaAccount(fHeaderView->fAccountID); + if (fHeaderView->AccountID() != ~0L) + fMail->SendViaAccount(fHeaderView->AccountID()); result = fMail->Send(now); @@ -2571,12 +2560,12 @@ TMailWindow::SaveAsDraft() { char fileName[B_FILE_NAME_LENGTH]; // save as some version of the message's subject - if (strlen(fHeaderView->fSubject->Text()) == 0) + if (fHeaderView->IsSubjectEmpty()) { strlcpy(fileName, B_TRANSLATE("Untitled"), sizeof(fileName)); - else - strlcpy(fileName, fHeaderView->fSubject->Text(), - sizeof(fileName)); + } else { + strlcpy(fileName, fHeaderView->Subject(), sizeof(fileName)); + } uint32 originalLength = strlen(fileName); @@ -2623,25 +2612,26 @@ TMailWindow::SaveAsDraft() } // Write the content of the message - draft.Write(fContentView->fTextView->Text(), - fContentView->fTextView->TextLength()); + draft.Write(fContentView->TextView()->Text(), + fContentView->TextView()->TextLength()); // Add the header stuff as attributes - WriteAttrString(&draft, B_MAIL_ATTR_NAME, fHeaderView->fTo->Text()); - WriteAttrString(&draft, B_MAIL_ATTR_TO, fHeaderView->fTo->Text()); - WriteAttrString(&draft, B_MAIL_ATTR_SUBJECT, fHeaderView->fSubject->Text()); - if (fHeaderView->fCc != NULL) - WriteAttrString(&draft, B_MAIL_ATTR_CC, fHeaderView->fCc->Text()); - if (fHeaderView->fBcc != NULL) - WriteAttrString(&draft, B_MAIL_ATTR_BCC, fHeaderView->fBcc->Text()); + WriteAttrString(&draft, B_MAIL_ATTR_NAME, fHeaderView->To()); + WriteAttrString(&draft, B_MAIL_ATTR_TO, fHeaderView->To()); + WriteAttrString(&draft, B_MAIL_ATTR_SUBJECT, fHeaderView->Subject()); + if (!fHeaderView->IsCcEmpty()) + WriteAttrString(&draft, B_MAIL_ATTR_CC, fHeaderView->Cc()); + if (!fHeaderView->IsBccEmpty()) + WriteAttrString(&draft, B_MAIL_ATTR_BCC, fHeaderView->Bcc()); // Add account - BMenuItem* menuItem = fHeaderView->fAccountMenu->FindMarked(); - if (menuItem != NULL) - WriteAttrString(&draft, B_MAIL_ATTR_ACCOUNT, menuItem->Label()); + if (fHeaderView->AccountName() != NULL) { + WriteAttrString(&draft, B_MAIL_ATTR_ACCOUNT, + fHeaderView->AccountName()); + } // Add encoding - menuItem = fHeaderView->fEncodingMenu->FindMarked(); + BMenuItem* menuItem = fEncodingMenu->FindMarked(); if (menuItem != NULL) WriteAttrString(&draft, "MAIL:encoding", menuItem->Label()); @@ -2838,7 +2828,7 @@ TMailWindow::OpenMessage(const entry_ref* ref, uint32 characterSetForDecoding) fPrevTrackerPositionSaved = false; fNextTrackerPositionSaved = false; - fContentView->fTextView->StopLoad(); + fContentView->TextView()->StopLoad(); delete fMail; fMail = NULL; @@ -2870,28 +2860,25 @@ TMailWindow::OpenMessage(const entry_ref* ref, uint32 characterSetForDecoding) // Load the raw UTF-8 text from the file. file.GetSize(&size); - fContentView->fTextView->SetText(&file, 0, size); + fContentView->TextView()->SetText(&file, 0, size); // Restore Fields from attributes if (node.ReadAttrString(B_MAIL_ATTR_TO, &string) == B_OK) - fHeaderView->fTo->SetText(string.String()); + fHeaderView->SetTo(string); if (node.ReadAttrString(B_MAIL_ATTR_SUBJECT, &string) == B_OK) - fHeaderView->fSubject->SetText(string.String()); + fHeaderView->SetSubject(string); if (node.ReadAttrString(B_MAIL_ATTR_CC, &string) == B_OK) - fHeaderView->fCc->SetText(string.String()); + fHeaderView->SetCc(string); if (node.ReadAttrString(B_MAIL_ATTR_BCC, &string) == B_OK) - fHeaderView->fBcc->SetText(string.String()); + fHeaderView->SetBcc(string); // Restore account - if (node.ReadAttrString(B_MAIL_ATTR_ACCOUNT, &string) == B_OK) { - BMenuItem* accountItem = fHeaderView->fAccountMenu->FindItem(string.String()); - if (accountItem != NULL) - accountItem->SetMarked(true); - } + if (node.ReadAttrString(B_MAIL_ATTR_ACCOUNT, &string) == B_OK) + fHeaderView->SetAccount(string); // Restore encoding if (node.ReadAttrString("MAIL:encoding", &string) == B_OK) { - BMenuItem* encodingItem = fHeaderView->fEncodingMenu->FindItem(string.String()); + BMenuItem* encodingItem = fEncodingMenu->FindItem(string.String()); if (encodingItem != NULL) encodingItem->SetMarked(true); } @@ -2923,7 +2910,7 @@ TMailWindow::OpenMessage(const entry_ref* ref, uint32 characterSetForDecoding) // A real mail message, parse its headers to get from, to, etc. fMail = new BEmailMessage(fRef, characterSetForDecoding); fIncoming = true; - fHeaderView->LoadMessage(fMail); + fHeaderView->SetFromMessage(fMail); } err = fMail->InitCheck(); @@ -2977,9 +2964,9 @@ TMailWindow::OpenMessage(const entry_ref* ref, uint32 characterSetForDecoding) } // Clear out existing contents of text view. - fContentView->fTextView->SetText("", (int32)0); + fContentView->TextView()->SetText("", (int32)0); - fContentView->fTextView->LoadMessage(fMail, false, NULL); + fContentView->TextView()->LoadMessage(fMail, false, NULL); if (fApp->ShowToolBar()) _UpdateReadButton(); @@ -3003,31 +2990,6 @@ TMailWindow::FrontmostWindow() // #pragma mark - -void -TMailWindow::_UpdateSizeLimits() -{ - float minWidth, maxWidth, minHeight, maxHeight; - GetSizeLimits(&minWidth, &maxWidth, &minHeight, &maxHeight); - - float height; - fMenuBar->GetPreferredSize(&minWidth, &height); - - minHeight = height; - - if (fToolBar != NULL) { - minWidth = fToolBar->MinSize().width; - height = fToolBar->MinSize().height; - minHeight += height; - } else { - minWidth = WIND_WIDTH; - } - - minHeight += fHeaderView->Bounds().Height() + ENCLOSURES_HEIGHT + 60; - - SetSizeLimits(minWidth, RIGHT_BOUNDARY, minHeight, RIGHT_BOUNDARY); -} - - status_t TMailWindow::_GetQueryPath(BPath* queryPath) const { @@ -3232,3 +3194,17 @@ TMailWindow::_SetDownloading(bool downloading) { fDownloading = downloading; } + + +uint32 +TMailWindow::_CurrentCharacterSet() const +{ + uint32 defaultCharSet = fResending || !fIncoming + ? fApp->MailCharacterSet() : B_MAIL_NULL_CONVERSION; + + BMenuItem* marked = fEncodingMenu->FindMarked(); + if (marked == NULL) + return defaultCharSet; + + return marked->Message()->GetInt32("charset", defaultCharSet); +} diff --git a/src/apps/mail/MailWindow.h b/src/apps/mail/MailWindow.h index 6713f6eb85..5f34fa847d 100644 --- a/src/apps/mail/MailWindow.h +++ b/src/apps/mail/MailWindow.h @@ -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; diff --git a/src/apps/mail/People.cpp b/src/apps/mail/People.cpp new file mode 100644 index 0000000000..17b69f4978 --- /dev/null +++ b/src/apps/mail/People.cpp @@ -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 +#include + + +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! +} diff --git a/src/apps/mail/People.h b/src/apps/mail/People.h new file mode 100644 index 0000000000..1d53e4617f --- /dev/null +++ b/src/apps/mail/People.h @@ -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 + +#include +#include +#include +#include + +#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 PersonMap; + + QueryList& fQueryList; + BObjectList 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 StringCountMap; + + QueryList& fQueryList; + BStringList fGroups; + StringCountMap fGroupMap; +}; + + +#endif // ADDRESS_TEXT_CONTROL_H + diff --git a/src/apps/mail/QueryList.cpp b/src/apps/mail/QueryList.cpp new file mode 100644 index 0000000000..1153acb6ff --- /dev/null +++ b/src/apps/mail/QueryList.cpp @@ -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 +#include +#include +#include + + +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(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; +} diff --git a/src/apps/mail/QueryList.h b/src/apps/mail/QueryList.h new file mode 100644 index 0000000000..1a95ee364e --- /dev/null +++ b/src/apps/mail/QueryList.h @@ -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 +#include + +#include +#include +#include +#include +#include + + +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 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 ThreadVector; + typedef std::vector QueryVector; + + bool fQuit; + RefMap fRefs; + QueryVector fQueries; + QueryVector fQueryQueue; + ThreadVector fFetchThreads; + BObjectList fListeners; +}; + + +#endif // QUERY_LIST_H