| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "chrome/browser/sync/notifier/communicator/mailbox.h" | |
| 6 | |
| 7 #include <assert.h> | |
| 8 #include <stdlib.h> | |
| 9 | |
| 10 #include <stack> | |
| 11 #include <vector> | |
| 12 | |
| 13 #include "chrome/browser/sync/notifier/base/string.h" | |
| 14 #include "chrome/browser/sync/notifier/base/utils.h" | |
| 15 #include "chrome/browser/sync/notifier/communicator/xml_parse_helpers.h" | |
| 16 #include "talk/base/basictypes.h" | |
| 17 #include "talk/base/common.h" | |
| 18 #include "talk/base/stringutils.h" | |
| 19 #include "talk/xmllite/xmlelement.h" | |
| 20 #include "talk/xmpp/constants.h" | |
| 21 | |
| 22 namespace notifier { | |
| 23 | |
| 24 // Labels are a list of strings seperated by a '|' character. The '|' character | |
| 25 // is escaped with a backslash ('\\') and the backslash is also escaped with a | |
| 26 // backslash. | |
| 27 static void ParseLabelSet(const std::string& text, | |
| 28 MessageThread::StringSet* labels) { | |
| 29 const char* input_cur = text.c_str(); | |
| 30 const char* input_end = input_cur + text.size(); | |
| 31 char* result = new char[text.size() + 1]; | |
| 32 char* next_write = result; | |
| 33 | |
| 34 while (input_cur < input_end) { | |
| 35 if (*input_cur == '|') { | |
| 36 if (next_write != result) { | |
| 37 *next_write = '\0'; | |
| 38 labels->insert(std::string(result)); | |
| 39 next_write = result; | |
| 40 } | |
| 41 input_cur++; | |
| 42 continue; | |
| 43 } | |
| 44 | |
| 45 if (*input_cur == '\\') { | |
| 46 // Skip a character in the input and break if we are at the end. | |
| 47 input_cur++; | |
| 48 if (input_cur >= input_end) | |
| 49 break; | |
| 50 } | |
| 51 *next_write = *input_cur; | |
| 52 next_write++; | |
| 53 input_cur++; | |
| 54 } | |
| 55 | |
| 56 if (next_write != result) { | |
| 57 *next_write = '\0'; | |
| 58 labels->insert(std::string(result)); | |
| 59 } | |
| 60 | |
| 61 delete [] result; | |
| 62 } | |
| 63 | |
| 64 // ----------------------------------------------------------------------------- | |
| 65 | |
| 66 std::string MailAddress::safe_name() const { | |
| 67 if (!name().empty()) { | |
| 68 return name(); | |
| 69 } | |
| 70 | |
| 71 if (!address().empty()) { | |
| 72 size_t at = address().find('@'); | |
| 73 if (at == std::string::npos) { | |
| 74 return address(); | |
| 75 } | |
| 76 | |
| 77 if (at != 0) { | |
| 78 return address().substr(0, at); | |
| 79 } | |
| 80 } | |
| 81 | |
| 82 return std::string("(unknown)"); | |
| 83 } | |
| 84 | |
| 85 // ----------------------------------------------------------------------------- | |
| 86 MessageThread::~MessageThread() { | |
| 87 Clear(); | |
| 88 } | |
| 89 | |
| 90 void MessageThread::Clear() { | |
| 91 delete labels_; | |
| 92 labels_ = NULL; | |
| 93 | |
| 94 delete senders_; | |
| 95 senders_ = NULL; | |
| 96 } | |
| 97 | |
| 98 MessageThread& MessageThread::operator=(const MessageThread& r) { | |
| 99 if (&r != this) { | |
| 100 Clear(); | |
| 101 // Copy everything. | |
| 102 r.AssertValid(); | |
| 103 thread_id_ = r.thread_id_; | |
| 104 date64_ = r.date64_; | |
| 105 message_count_ = r.message_count_; | |
| 106 personal_level_ = r.personal_level_; | |
| 107 subject_ = r.subject_; | |
| 108 snippet_ = r.snippet_; | |
| 109 | |
| 110 if (r.labels_) | |
| 111 labels_ = new StringSet(*r.labels_); | |
| 112 else | |
| 113 labels_ = new StringSet; | |
| 114 if (r.senders_) | |
| 115 senders_ = new MailSenderList(*r.senders_); | |
| 116 else | |
| 117 senders_ = new MailSenderList; | |
| 118 } | |
| 119 AssertValid(); | |
| 120 return *this; | |
| 121 } | |
| 122 | |
| 123 MessageThread* MessageThread::CreateFromXML( | |
| 124 const buzz::XmlElement* src) { | |
| 125 MessageThread* info = new MessageThread(); | |
| 126 if (!info || !info->InitFromXml(src)) { | |
| 127 delete info; | |
| 128 return NULL; | |
| 129 } | |
| 130 return info; | |
| 131 } | |
| 132 | |
| 133 // Init from a chunk of XML. | |
| 134 bool MessageThread::InitFromXml(const buzz::XmlElement* src) { | |
| 135 labels_ = new StringSet; | |
| 136 senders_ = new MailSenderList; | |
| 137 | |
| 138 if (src->Name() != buzz::kQnMailThreadInfo) | |
| 139 return false; | |
| 140 | |
| 141 if (!ParseInt64Attr(src, buzz::kQnMailTid, &thread_id_)) | |
| 142 return false; | |
| 143 if (!ParseInt64Attr(src, buzz::kQnMailDate, &date64_)) | |
| 144 return false; | |
| 145 if (!ParseIntAttr(src, buzz::kQnMailMessages, &message_count_)) | |
| 146 return false; | |
| 147 if (!ParseIntAttr(src, buzz::kQnMailParticipation, &personal_level_)) | |
| 148 return false; | |
| 149 | |
| 150 const buzz::XmlElement* senders = src->FirstNamed(buzz::kQnMailSenders); | |
| 151 if (!senders) | |
| 152 return false; | |
| 153 for (const buzz::XmlElement* child = senders->FirstElement(); | |
| 154 child != NULL; | |
| 155 child = child->NextElement()) { | |
| 156 if (child->Name() != buzz::kQnMailSender) | |
| 157 continue; | |
| 158 std::string address; | |
| 159 if (!ParseStringAttr(child, buzz::kQnMailAddress, &address)) | |
| 160 continue; | |
| 161 std::string name; | |
| 162 ParseStringAttr(child, buzz::kQnMailName, &name); | |
| 163 bool originator = false; | |
| 164 ParseBoolAttr(child, buzz::kQnMailOriginator, &originator); | |
| 165 bool unread = false; | |
| 166 ParseBoolAttr(child, buzz::kQnMailUnread, &unread); | |
| 167 | |
| 168 senders_->push_back(MailSender(name, address, unread, originator)); | |
| 169 } | |
| 170 | |
| 171 const buzz::XmlElement* labels = src->FirstNamed(buzz::kQnMailLabels); | |
| 172 if (!labels) | |
| 173 return false; | |
| 174 ParseLabelSet(labels->BodyText(), labels_); | |
| 175 | |
| 176 const buzz::XmlElement* subject = src->FirstNamed(buzz::kQnMailSubject); | |
| 177 if (!subject) | |
| 178 return false; | |
| 179 subject_ = subject->BodyText(); | |
| 180 | |
| 181 const buzz::XmlElement* snippet = src->FirstNamed(buzz::kQnMailSnippet); | |
| 182 if (!snippet) | |
| 183 return false; | |
| 184 snippet_ = snippet->BodyText(); | |
| 185 | |
| 186 AssertValid(); | |
| 187 return true; | |
| 188 } | |
| 189 | |
| 190 bool MessageThread::starred() const { | |
| 191 return (labels_->find("^t") != labels_->end()); | |
| 192 } | |
| 193 | |
| 194 bool MessageThread::unread() const { | |
| 195 return (labels_->find("^u") != labels_->end()); | |
| 196 } | |
| 197 | |
| 198 #if defined(DEBUG) | |
| 199 // Non-debug version is inline and empty. | |
| 200 void MessageThread::AssertValid() const { | |
| 201 assert(thread_id_ != 0); | |
| 202 assert(senders_ != NULL); | |
| 203 // In some (odd) cases, gmail may send email with no sender. | |
| 204 // assert(!senders_->empty()); | |
| 205 assert(message_count_ > 0); | |
| 206 assert(labels_ != NULL); | |
| 207 } | |
| 208 #endif | |
| 209 | |
| 210 MailBox* MailBox::CreateFromXML(const buzz::XmlElement* src) { | |
| 211 MailBox* mail_box = new MailBox(); | |
| 212 if (!mail_box || !mail_box->InitFromXml(src)) { | |
| 213 delete mail_box; | |
| 214 return NULL; | |
| 215 } | |
| 216 return mail_box; | |
| 217 } | |
| 218 | |
| 219 // Init from a chunk of XML. | |
| 220 bool MailBox::InitFromXml(const buzz::XmlElement* src) { | |
| 221 if (src->Name() != buzz::kQnMailBox) | |
| 222 return false; | |
| 223 | |
| 224 if (!ParseIntAttr(src, buzz::kQnMailTotalMatched, &mailbox_size_)) | |
| 225 return false; | |
| 226 | |
| 227 estimate_ = false; | |
| 228 ParseBoolAttr(src, buzz::kQnMailTotalEstimate, &estimate_); | |
| 229 | |
| 230 first_index_ = 0; | |
| 231 ParseIntAttr(src, buzz::kQnMailFirstIndex, &first_index_); | |
| 232 | |
| 233 result_time_ = 0; | |
| 234 ParseInt64Attr(src, buzz::kQnMailResultTime, &result_time_); | |
| 235 | |
| 236 highest_thread_id_ = 0; | |
| 237 | |
| 238 const buzz::XmlElement* thread_element = src->FirstNamed(buzz::kQnMailThreadIn
fo); | |
| 239 while (thread_element) { | |
| 240 MessageThread* thread = MessageThread::CreateFromXML(thread_element); | |
| 241 if (thread) { | |
| 242 if (thread->thread_id() > highest_thread_id_) | |
| 243 highest_thread_id_ = thread->thread_id(); | |
| 244 threads_.push_back(MessageThreadPointer(thread)); | |
| 245 } | |
| 246 thread_element = thread_element->NextNamed(buzz::kQnMailThreadInfo); | |
| 247 } | |
| 248 return true; | |
| 249 } | |
| 250 | |
| 251 const size_t kMaxShortnameLength = 12; | |
| 252 | |
| 253 // Tip: If you extend this list of chars, do not include '-'. | |
| 254 const char name_delim[] = " ,.:;\'\"()[]{}<>*@"; | |
| 255 | |
| 256 class SenderFormatter { | |
| 257 public: | |
| 258 // sender should not be deleted while this class is in use. | |
| 259 SenderFormatter(const MailSender& sender, | |
| 260 const std::string& me_address) | |
| 261 : sender_(sender), | |
| 262 visible_(false), | |
| 263 short_format_(true), | |
| 264 space_(kMaxShortnameLength) { | |
| 265 me_ = talk_base::ascicmp(me_address.c_str(), | |
| 266 sender.address().c_str()) == 0; | |
| 267 } | |
| 268 | |
| 269 bool visible() const { | |
| 270 return visible_; | |
| 271 } | |
| 272 | |
| 273 bool is_unread() const { | |
| 274 return sender_.unread(); | |
| 275 } | |
| 276 | |
| 277 const std::string name() const { | |
| 278 return name_; | |
| 279 } | |
| 280 | |
| 281 void set_short_format(bool short_format) { | |
| 282 short_format_ = short_format; | |
| 283 UpdateName(); | |
| 284 } | |
| 285 | |
| 286 void set_visible(bool visible) { | |
| 287 visible_ = visible; | |
| 288 UpdateName(); | |
| 289 } | |
| 290 | |
| 291 void set_space(size_t space) { | |
| 292 space_ = space; | |
| 293 UpdateName(); | |
| 294 } | |
| 295 | |
| 296 private: | |
| 297 // Attempt to shorten to the first word in a person's name We could revisit | |
| 298 // and do better at international punctuation, but this is what cricket did, | |
| 299 // and it should be removed soon when gmail does the notification instead of | |
| 300 // us forming it on the client. | |
| 301 static void ShortenName(std::string* name) { | |
| 302 size_t start = name->find_first_not_of(name_delim); | |
| 303 if (start != std::string::npos && start > 0) { | |
| 304 name->erase(0, start); | |
| 305 } | |
| 306 start = name->find_first_of(name_delim); | |
| 307 if (start != std::string::npos) { | |
| 308 name->erase(start); | |
| 309 } | |
| 310 } | |
| 311 | |
| 312 void UpdateName() { | |
| 313 // Update the name if is going to be used. | |
| 314 if (!visible_) { | |
| 315 return; | |
| 316 } | |
| 317 | |
| 318 if (me_) { | |
| 319 name_ = "me"; | |
| 320 return; | |
| 321 } | |
| 322 | |
| 323 if (sender_.name().empty() && sender_.address().empty()) { | |
| 324 name_ = ""; | |
| 325 return; | |
| 326 } | |
| 327 | |
| 328 name_ = sender_.name(); | |
| 329 // Handle the case of no name or a name looks like an email address. When | |
| 330 // mail is sent to "Quality@example.com" <quality-team@example.com>, we | |
| 331 // shouldn't show "Quality@example.com" as the name. Instead, use the email | |
| 332 // address (without the @...) | |
| 333 if (name_.empty() || name_.find_first_of("@") != std::string::npos) { | |
| 334 name_ = sender_.address(); | |
| 335 size_t at_index = name_.find_first_of("@"); | |
| 336 if (at_index != std::string::npos) { | |
| 337 name_.erase(at_index); | |
| 338 } | |
| 339 } else if (short_format_) { | |
| 340 ShortenName(&name_); | |
| 341 } | |
| 342 | |
| 343 if (name_.empty()) { | |
| 344 name_ = "(unknown)"; | |
| 345 } | |
| 346 | |
| 347 // Abbreviate if too long. | |
| 348 if (name_.size() > space_) { | |
| 349 name_.replace(space_ - 1, std::string::npos, "."); | |
| 350 } | |
| 351 } | |
| 352 | |
| 353 const MailSender& sender_; | |
| 354 std::string name_; | |
| 355 bool visible_; | |
| 356 bool short_format_; | |
| 357 size_t space_; | |
| 358 bool me_; | |
| 359 DISALLOW_COPY_AND_ASSIGN(SenderFormatter); | |
| 360 }; | |
| 361 | |
| 362 const char kNormalSeparator[] = ", "; | |
| 363 const char kEllidedSeparator[] = " .."; | |
| 364 | |
| 365 std::string FormatName(const std::string& name, bool bold) { | |
| 366 std::string formatted_name; | |
| 367 if (bold) { | |
| 368 formatted_name.append("<b>"); | |
| 369 } | |
| 370 formatted_name.append(HtmlEncode(name)); | |
| 371 if (bold) { | |
| 372 formatted_name.append("</b>"); | |
| 373 } | |
| 374 return formatted_name; | |
| 375 } | |
| 376 | |
| 377 class SenderFormatterList { | |
| 378 public: | |
| 379 // sender_list must not be deleted while this class is being used. | |
| 380 SenderFormatterList(const MailSenderList& sender_list, | |
| 381 const std::string& me_address) | |
| 382 : state_(INITIAL_STATE), | |
| 383 are_any_read_(false), | |
| 384 index_(-1), | |
| 385 first_unread_index_(-1) { | |
| 386 // Add all read messages. | |
| 387 const MailSender* originator = NULL; | |
| 388 bool any_unread = false; | |
| 389 for (size_t i = 0; i < sender_list.size(); ++i) { | |
| 390 if (sender_list[i].originator()) { | |
| 391 originator = &sender_list[i]; | |
| 392 } | |
| 393 if (sender_list[i].unread()) { | |
| 394 any_unread = true; | |
| 395 continue; | |
| 396 } | |
| 397 are_any_read_ = true; | |
| 398 if (!sender_list[i].originator()) { | |
| 399 senders_.push_back(new SenderFormatter(sender_list[i], | |
| 400 me_address)); | |
| 401 } | |
| 402 } | |
| 403 | |
| 404 // There may not be an orignator (if there no senders). | |
| 405 if (originator) { | |
| 406 senders_.insert(senders_.begin(), new SenderFormatter(*originator, | |
| 407 me_address)); | |
| 408 } | |
| 409 | |
| 410 // Add all unread messages. | |
| 411 if (any_unread) { | |
| 412 // It should be rare, but there may be cases when all of the senders | |
| 413 // appear to have read the message. | |
| 414 first_unread_index_ = is_first_unread() ? 0 : senders_.size(); | |
| 415 for (size_t i = 0; i < sender_list.size(); ++i) { | |
| 416 if (!sender_list[i].unread()) { | |
| 417 continue; | |
| 418 } | |
| 419 // Don't add the originator if it is already at the start of the | |
| 420 // "unread" list. | |
| 421 if (sender_list[i].originator() && is_first_unread()) { | |
| 422 continue; | |
| 423 } | |
| 424 senders_.push_back(new SenderFormatter(sender_list[i], me_address)); | |
| 425 } | |
| 426 } | |
| 427 } | |
| 428 | |
| 429 ~SenderFormatterList() { | |
| 430 CleanupSequence(&senders_); | |
| 431 } | |
| 432 | |
| 433 std::string GetHtml(int space) { | |
| 434 index_ = -1; | |
| 435 state_ = INITIAL_STATE; | |
| 436 while (!added_.empty()) { | |
| 437 added_.pop(); | |
| 438 } | |
| 439 | |
| 440 // If there is only one sender, let it take up all of the space. | |
| 441 if (senders_.size() == 1) { | |
| 442 senders_[0]->set_space(space); | |
| 443 senders_[0]->set_short_format(false); | |
| 444 } | |
| 445 | |
| 446 int length = 1; | |
| 447 // Add as many senders as we can in the given space. Computes the visible | |
| 448 // length at each iteration, but does not construct the actual html. | |
| 449 while (length < space && AddNextSender()) { | |
| 450 int new_length = ConstructHtml(is_first_unread(), NULL); | |
| 451 // Remove names to avoid truncating | |
| 452 // * if there will be at least 2 left or | |
| 453 // * if the spacing <= 2 characters per sender and there | |
| 454 // is at least one left. | |
| 455 if ((new_length > space && | |
| 456 (visible_count() > 2 || | |
| 457 (ComputeSpacePerSender(space) <= 2 && visible_count() > 1)))) { | |
| 458 RemoveLastAddedSender(); | |
| 459 break; | |
| 460 } | |
| 461 length = new_length; | |
| 462 } | |
| 463 | |
| 464 if (length > space) { | |
| 465 int max = ComputeSpacePerSender(space); | |
| 466 for (size_t i = 0; i < senders_.size(); ++i) { | |
| 467 if (senders_[i]->visible()) { | |
| 468 senders_[i]->set_space(max); | |
| 469 } | |
| 470 } | |
| 471 } | |
| 472 | |
| 473 // Now construct the actual html. | |
| 474 std::string html_list; | |
| 475 length = ConstructHtml(is_first_unread(), &html_list); | |
| 476 if (length > space) { | |
| 477 LOG(LS_WARNING) << "LENGTH: " << length << " exceeds " | |
| 478 << space << " " << html_list; | |
| 479 } | |
| 480 return html_list; | |
| 481 } | |
| 482 | |
| 483 private: | |
| 484 int ComputeSpacePerSender(int space) const { | |
| 485 // Why the "- 2"? To allow for the " .. " which may occur after the names, | |
| 486 // and no matter what always allow at least 2 characters per sender. | |
| 487 return talk_base::_max<int>(space / visible_count() - 2, 2); | |
| 488 } | |
| 489 | |
| 490 // Finds the next sender that should be added to the "from" list and sets it | |
| 491 // to visible. | |
| 492 // | |
| 493 // This method may be called until it returns false or until | |
| 494 // RemoveLastAddedSender is called. | |
| 495 bool AddNextSender() { | |
| 496 // The progression is: | |
| 497 // 1. Add the person who started the thread, which is the first message. | |
| 498 // 2. Add the first unread message (unless it has already been added). | |
| 499 // 3. Add the last message (unless it has already been added). | |
| 500 // 4. Add the message that is just before the last message processed | |
| 501 // (unless it has already been added). | |
| 502 // If there is no message (i.e. at index -1), return false. | |
| 503 // | |
| 504 // Typically, this method is called until it returns false or all of the | |
| 505 // space available is used. | |
| 506 switch (state_) { | |
| 507 case INITIAL_STATE: | |
| 508 state_ = FIRST_MESSAGE; | |
| 509 index_ = 0; | |
| 510 // If the server behaves odd and doesn't send us any senders, do | |
| 511 // something graceful. | |
| 512 if (senders_.size() == 0) { | |
| 513 return false; | |
| 514 } | |
| 515 break; | |
| 516 | |
| 517 case FIRST_MESSAGE: | |
| 518 if (first_unread_index_ >= 0) { | |
| 519 state_ = FIRST_UNREAD_MESSAGE; | |
| 520 index_ = first_unread_index_; | |
| 521 break; | |
| 522 } | |
| 523 // Fall through. | |
| 524 case FIRST_UNREAD_MESSAGE: | |
| 525 state_ = LAST_MESSAGE; | |
| 526 index_ = senders_.size() - 1; | |
| 527 break; | |
| 528 | |
| 529 case LAST_MESSAGE: | |
| 530 case PREVIOUS_MESSAGE: | |
| 531 state_ = PREVIOUS_MESSAGE; | |
| 532 index_--; | |
| 533 break; | |
| 534 | |
| 535 case REMOVED_MESSAGE: | |
| 536 default: | |
| 537 ASSERT(false); | |
| 538 return false; | |
| 539 } | |
| 540 | |
| 541 if (index_ < 0) { | |
| 542 return false; | |
| 543 } | |
| 544 | |
| 545 if (!senders_[index_]->visible()) { | |
| 546 added_.push(index_); | |
| 547 senders_[index_]->set_visible(true); | |
| 548 } | |
| 549 return true; | |
| 550 } | |
| 551 | |
| 552 // Makes the last added sender not visible. | |
| 553 // | |
| 554 // May be called while visible_count() > 0. | |
| 555 void RemoveLastAddedSender() { | |
| 556 state_ = REMOVED_MESSAGE; | |
| 557 int index = added_.top(); | |
| 558 added_.pop(); | |
| 559 senders_[index]->set_visible(false); | |
| 560 } | |
| 561 | |
| 562 // Constructs the html of the SenderList and returns the length of the | |
| 563 // visible text. | |
| 564 // | |
| 565 // The algorithm simply walks down the list of Senders, appending the html | |
| 566 // for each visible sender, and adding ellipsis or commas in between, | |
| 567 // whichever is appropriate. | |
| 568 // | |
| 569 // html Filled with html. Maybe NULL if the html doesn't need to be | |
| 570 // constructed yet (useful for simply determining the length of the | |
| 571 // visible text). | |
| 572 // | |
| 573 // returns The approximate visible length of the html. | |
| 574 int ConstructHtml(bool first_is_unread, | |
| 575 std::string* html) const { | |
| 576 if (senders_.empty()) { | |
| 577 return 0; | |
| 578 } | |
| 579 | |
| 580 int length = 0; | |
| 581 | |
| 582 // The first is always visible. | |
| 583 const SenderFormatter* sender = senders_[0]; | |
| 584 const std::string& originator_name = sender->name(); | |
| 585 length += originator_name.length(); | |
| 586 if (html) { | |
| 587 html->append(FormatName(originator_name, first_is_unread)); | |
| 588 } | |
| 589 | |
| 590 bool elided = false; | |
| 591 const char* between = ""; | |
| 592 for (size_t i = 1; i < senders_.size(); i++) { | |
| 593 sender = senders_[i]; | |
| 594 | |
| 595 if (sender->visible()) { | |
| 596 // Handle the separator. | |
| 597 between = elided ? " " : kNormalSeparator; | |
| 598 // Ignore the , for length because it is so narrow, so in both cases | |
| 599 // above the space is the only things that counts for spaces. | |
| 600 length++; | |
| 601 | |
| 602 // Handle the name. | |
| 603 const std::string name = sender->name(); | |
| 604 length += name.size(); | |
| 605 | |
| 606 // Construct the html. | |
| 607 if (html) { | |
| 608 html->append(between); | |
| 609 html->append(FormatName(name, sender->is_unread())); | |
| 610 } | |
| 611 elided = false; | |
| 612 } else if (!elided) { | |
| 613 between = kEllidedSeparator; | |
| 614 length += 2; // ".." is narrow. | |
| 615 if (html) { | |
| 616 html->append(between); | |
| 617 } | |
| 618 elided = true; | |
| 619 } | |
| 620 } | |
| 621 return length; | |
| 622 } | |
| 623 | |
| 624 bool is_first_unread() const { | |
| 625 return !are_any_read_; | |
| 626 } | |
| 627 | |
| 628 size_t visible_count() const { | |
| 629 return added_.size(); | |
| 630 } | |
| 631 | |
| 632 enum MessageState { | |
| 633 INITIAL_STATE, | |
| 634 FIRST_MESSAGE, | |
| 635 FIRST_UNREAD_MESSAGE, | |
| 636 LAST_MESSAGE, | |
| 637 PREVIOUS_MESSAGE, | |
| 638 REMOVED_MESSAGE, | |
| 639 }; | |
| 640 | |
| 641 // What state we were in during the last "stateful" function call. | |
| 642 MessageState state_; | |
| 643 bool are_any_read_; | |
| 644 std::vector<SenderFormatter*> senders_; | |
| 645 std::stack<int> added_; | |
| 646 int index_; | |
| 647 int first_unread_index_; | |
| 648 DISALLOW_COPY_AND_ASSIGN(SenderFormatterList); | |
| 649 }; | |
| 650 | |
| 651 | |
| 652 std::string GetSenderHtml(const MailSenderList& sender_list, | |
| 653 int message_count, | |
| 654 const std::string& me_address, | |
| 655 int space) { | |
| 656 // There has to be at least 9 spaces to show something reasonable. | |
| 657 ASSERT(space >= 10); | |
| 658 std::string count_html; | |
| 659 if (message_count > 1) { | |
| 660 std::string count(IntToString(message_count)); | |
| 661 space -= (count.size()); | |
| 662 count_html.append(" ("); | |
| 663 count_html.append(count); | |
| 664 count_html.append(")"); | |
| 665 // Reduce space due to parenthesis and . | |
| 666 space -= 2; | |
| 667 } | |
| 668 | |
| 669 SenderFormatterList senders(sender_list, me_address); | |
| 670 std::string html_list(senders.GetHtml(space)); | |
| 671 html_list.append(count_html); | |
| 672 return html_list; | |
| 673 } | |
| 674 | |
| 675 } // namespace notifier | |
| OLD | NEW |