// This method removes the thread at threadIndex from the view 
// and puts it back in its new position, determined by the sort order.
// And, if the selection is affected, save and restore the selection.
void nsMsgThreadedDBView::MoveThreadAt(nsMsgViewIndex threadIndex)
{
  // we need to check if the thread is collapsed or not...
  // We want to turn off tree notifications so that we don't
  // reload the current message.
  // We also need to invalidate the range between where the thread was
  // and where it ended up.
  bool changesDisabled = mSuppressChangeNotification;
  if (!changesDisabled)
    SetSuppressChangeNotifications(true);

  nsCOMPtr <nsIMsgDBHdr> threadHdr;

  GetMsgHdrForViewIndex(threadIndex, getter_AddRefs(threadHdr));
  PRInt32 childCount = 0;

  nsMsgKey preservedKey;
  nsAutoTArray<nsMsgKey, 1> preservedSelection;
  PRInt32 selectionCount;
  PRInt32 currentIndex;
  bool hasSelection = mTree && mTreeSelection &&
                        ((NS_SUCCEEDED(mTreeSelection->GetCurrentIndex(&currentIndex)) &&
                         currentIndex >= 0 && (PRUint32)currentIndex < GetSize()) ||
                         (NS_SUCCEEDED(mTreeSelection->GetRangeCount(&selectionCount)) &&
                          selectionCount > 0));
  if (hasSelection)
    SaveAndClearSelection(&preservedKey, preservedSelection);
  PRUint32 saveFlags = m_flags[threadIndex];
  bool threadIsExpanded = !(saveFlags & nsMsgMessageFlags::Elided);

  if (threadIsExpanded)
  {
    ExpansionDelta(threadIndex, &childCount);
    childCount = -childCount;
  }
  nsTArray<nsMsgKey> threadKeys;
  nsTArray<PRUint32> threadFlags;
  nsTArray<PRUint8> threadLevels;

  if (threadIsExpanded)
  {
    threadKeys.SetCapacity(childCount);
    threadFlags.SetCapacity(childCount);
    threadLevels.SetCapacity(childCount);
    for (nsMsgViewIndex index = threadIndex + 1; 
        index < GetSize() && m_levels[index]; index++)
    {
      threadKeys.AppendElement(m_keys[index]);
      threadFlags.AppendElement(m_flags[index]);
      threadLevels.AppendElement(m_levels[index]);
    }
    PRUint32 collapseCount;
    CollapseByIndex(threadIndex, &collapseCount);
  }
  nsMsgDBView::RemoveByIndex(threadIndex);
  nsMsgViewIndex newIndex = nsMsgViewIndex_None;
  AddHdr(threadHdr, &newIndex);

  // AddHdr doesn't always set newIndex, and getting it to do so
  // is going to require some refactoring.
  if (newIndex == nsMsgViewIndex_None)
    newIndex = FindHdr(threadHdr);

  if (threadIsExpanded)
  {
    m_keys.InsertElementsAt(newIndex + 1, threadKeys);
    m_flags.InsertElementsAt(newIndex + 1, threadFlags);
    m_levels.InsertElementsAt(newIndex + 1, threadLevels);
  }
  if (newIndex == nsMsgViewIndex_None)
  {
     NS_WARNING("newIndex=-1 in MoveThreadAt");
     newIndex = 0;
  }
  m_flags[newIndex] = saveFlags;
  // unfreeze selection.
  if (hasSelection)
    RestoreSelection(preservedKey, preservedSelection);

  if (!changesDisabled)
    SetSuppressChangeNotifications(false);
  nsMsgViewIndex lowIndex = threadIndex < newIndex ? threadIndex : newIndex;
  nsMsgViewIndex highIndex = lowIndex == threadIndex ? newIndex : threadIndex;
  NoteChange(lowIndex, highIndex - lowIndex + childCount + 1,
             nsMsgViewNotificationCode::changed);
}
Esempio n. 2
0
// This method removes the thread at threadIndex from the view 
// and puts it back in its new position, determined by the sort order.
// And, if the selection is affected, save and restore the selection.
void nsMsgSearchDBView::MoveThreadAt(nsMsgViewIndex threadIndex)
{
  bool updatesSuppressed = mSuppressChangeNotification;
  // Turn off tree notifications so that we don't reload the current message.
  if (!updatesSuppressed)
    SetSuppressChangeNotifications(true);

  nsCOMPtr<nsIMsgDBHdr> threadHdr;
  GetMsgHdrForViewIndex(threadIndex, getter_AddRefs(threadHdr));

  uint32_t saveFlags = m_flags[threadIndex];
  bool threadIsExpanded = !(saveFlags & nsMsgMessageFlags::Elided);
  int32_t childCount = 0;
  nsMsgKey preservedKey;
  nsAutoTArray<nsMsgKey, 1> preservedSelection;
  int32_t selectionCount;
  int32_t currentIndex;
  bool hasSelection = mTree && mTreeSelection &&
                        ((NS_SUCCEEDED(mTreeSelection->GetCurrentIndex(&currentIndex)) &&
                         currentIndex >= 0 && (uint32_t)currentIndex < GetSize()) ||
                         (NS_SUCCEEDED(mTreeSelection->GetRangeCount(&selectionCount)) &&
                          selectionCount > 0));


  if (hasSelection)
    SaveAndClearSelection(&preservedKey, preservedSelection);

  if (threadIsExpanded)
  {
    ExpansionDelta(threadIndex, &childCount);
    childCount = -childCount;
  }
  nsTArray<nsMsgKey> threadKeys;
  nsTArray<uint32_t> threadFlags;
  nsTArray<uint8_t> threadLevels;
  nsCOMArray<nsIMsgFolder> threadFolders;

  if (threadIsExpanded)
  {
    threadKeys.SetCapacity(childCount);
    threadFlags.SetCapacity(childCount);
    threadLevels.SetCapacity(childCount);
    threadFolders.SetCapacity(childCount);
    for (nsMsgViewIndex index = threadIndex + 1; 
        index < (nsMsgViewIndex) GetSize() && m_levels[index]; index++)
    {
      threadKeys.AppendElement(m_keys[index]);
      threadFlags.AppendElement(m_flags[index]);
      threadLevels.AppendElement(m_levels[index]);
      threadFolders.AppendObject(m_folders[index]);
    }
    uint32_t collapseCount;
    CollapseByIndex(threadIndex, &collapseCount);
  }
  nsMsgDBView::RemoveByIndex(threadIndex);
  m_folders.RemoveObjectAt(threadIndex);
  nsMsgViewIndex newIndex = GetIndexForThread(threadHdr);
  NS_ASSERTION(newIndex == m_levels.Length() || !m_levels[newIndex],
                "inserting into middle of thread");
  if (newIndex == nsMsgViewIndex_None)
    newIndex = 0;
  nsMsgKey msgKey;
  uint32_t msgFlags;
  threadHdr->GetMessageKey(&msgKey);
  threadHdr->GetFlags(&msgFlags);
  InsertMsgHdrAt(newIndex, threadHdr, msgKey, msgFlags, 0);

  if (threadIsExpanded)
  {
    m_keys.InsertElementsAt(newIndex + 1, threadKeys);
    m_flags.InsertElementsAt(newIndex + 1, threadFlags);
    m_levels.InsertElementsAt(newIndex + 1, threadLevels);
    m_folders.InsertObjectsAt(threadFolders, newIndex + 1);
  }
  m_flags[newIndex] = saveFlags;
  // unfreeze selection.
  if (hasSelection)
    RestoreSelection(preservedKey, preservedSelection);

  if (!updatesSuppressed)
    SetSuppressChangeNotifications(false);
  nsMsgViewIndex lowIndex = threadIndex < newIndex ? threadIndex : newIndex;
  nsMsgViewIndex highIndex = lowIndex == threadIndex ? newIndex : threadIndex;
  NoteChange(lowIndex, highIndex - lowIndex + childCount + 1,
             nsMsgViewNotificationCode::changed);
}
nsresult nsMsgThreadedDBView::OnNewHeader(nsIMsgDBHdr *newHdr, nsMsgKey aParentKey, bool ensureListed)
{
  if (m_viewFlags & nsMsgViewFlagsType::kGroupBySort)
    return nsMsgGroupView::OnNewHeader(newHdr, aParentKey, ensureListed);

  NS_ENSURE_TRUE(newHdr, NS_MSG_MESSAGE_NOT_FOUND);

  nsMsgKey newKey;
  newHdr->GetMessageKey(&newKey);

  // views can override this behaviour, which is to append to view.
  // This is the mail behaviour, but threaded views want
  // to insert in order...
  PRUint32 msgFlags;
  newHdr->GetFlags(&msgFlags);
  if ((m_viewFlags & nsMsgViewFlagsType::kUnreadOnly) && !ensureListed &&
      (msgFlags & nsMsgMessageFlags::Read))
    return NS_OK;
  // Currently, we only add the header in a threaded view if it's a thread.
  // We used to check if this was the first header in the thread, but that's
  // a bit harder in the unreadOnly view. But we'll catch it below.

  // if not threaded display just add it to the view.
  if (!(m_viewFlags & nsMsgViewFlagsType::kThreadedDisplay))
    return AddHdr(newHdr);

  // need to find the thread we added this to so we can change the hasnew flag
  // added message to existing thread, but not to view
  // Fix flags on thread header.
  PRInt32 threadCount;
  PRUint32 threadFlags;
  bool moveThread = false;
  nsMsgViewIndex threadIndex = ThreadIndexOfMsg(newKey, nsMsgViewIndex_None, &threadCount, &threadFlags);
  bool threadRootIsDisplayed = false;

  nsCOMPtr <nsIMsgThread> threadHdr;
  m_db->GetThreadContainingMsgHdr(newHdr, getter_AddRefs(threadHdr));
  if (threadHdr && m_sortType == nsMsgViewSortType::byDate)
  {
    PRUint32 newestMsgInThread = 0, msgDate = 0;
    threadHdr->GetNewestMsgDate(&newestMsgInThread);
    newHdr->GetDateInSeconds(&msgDate);
    moveThread = (msgDate == newestMsgInThread);
  }

  if (threadIndex != nsMsgViewIndex_None)
  {
    threadRootIsDisplayed = (m_currentlyDisplayedViewIndex == threadIndex);
    PRUint32 flags = m_flags[threadIndex];
    if (!(flags & MSG_VIEW_FLAG_HASCHILDREN))
    {
      flags |= MSG_VIEW_FLAG_HASCHILDREN | MSG_VIEW_FLAG_ISTHREAD;
      if (!(m_viewFlags & nsMsgViewFlagsType::kUnreadOnly))
        flags |= nsMsgMessageFlags::Elided;
      m_flags[threadIndex] = flags;
    }

    if (!(flags & nsMsgMessageFlags::Elided))
    { // thread is expanded
      // insert child into thread
      // levels of other hdrs may have changed!
      PRUint32 newFlags = msgFlags;
      PRInt32 level = 0;
      nsMsgViewIndex insertIndex = threadIndex;
      if (aParentKey == nsMsgKey_None)
      {
        newFlags |= MSG_VIEW_FLAG_ISTHREAD | MSG_VIEW_FLAG_HASCHILDREN;
      }
      else
      {
        nsMsgViewIndex parentIndex = FindParentInThread(aParentKey, threadIndex);
        level = m_levels[parentIndex] + 1;
        insertIndex = GetInsertInfoForNewHdr(newHdr, parentIndex, level);
      }
      InsertMsgHdrAt(insertIndex, newHdr, newKey, newFlags, level);
      // the call to NoteChange() has to happen after we add the key
      // as NoteChange() will call RowCountChanged() which will call our GetRowCount()
      NoteChange(insertIndex, 1, nsMsgViewNotificationCode::insertOrDelete);

      if (aParentKey == nsMsgKey_None)
      {
        // this header is the new king! try collapsing the existing thread,
        // removing it, installing this header as king, and expanding it.
        CollapseByIndex(threadIndex, nsnull);
        // call base class, so child won't get promoted.
        // nsMsgDBView::RemoveByIndex(threadIndex);
        ExpandByIndex(threadIndex, nsnull);
      }
    }
    else if (aParentKey == nsMsgKey_None)
    {
      // if we have a collapsed thread which just got a new
      // top of thread, change the keys array.
      m_keys[threadIndex] = newKey;
    }

    // If this message is new, the thread is collapsed, it is the
    // root and it was displayed, expand it so that the user does
    // not find that their message has magically turned into a summary.
    if (msgFlags & nsMsgMessageFlags::New &&
        m_flags[threadIndex] & nsMsgMessageFlags::Elided &&
        threadRootIsDisplayed)
      ExpandByIndex(threadIndex, nsnull);

    if (moveThread)
      MoveThreadAt(threadIndex);
    else
      // note change, to update the parent thread's unread and total counts
      NoteChange(threadIndex, 1, nsMsgViewNotificationCode::changed);
  }
  else if (threadHdr)
    // adding msg to thread that's not in view.
    AddMsgToThreadNotInView(threadHdr, newHdr, ensureListed);

  return NS_OK;
}