// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "views/controls/table/group_table_view.h"

#include "base/compiler_specific.h"
#include "base/message_loop.h"
#include "base/task.h"
#include "ui/gfx/canvas.h"

namespace views {

static const COLORREF kSeparatorLineColor = RGB(208, 208, 208);
static const int kSeparatorLineThickness = 1;

const char GroupTableView::kViewClassName[] = "views/GroupTableView";

GroupTableView::GroupTableView(GroupTableModel* model,
                               const std::vector<TableColumn>& columns,
                               TableTypes table_type,
                               bool single_selection,
                               bool resizable_columns,
                               bool autosize_columns,
                               bool draw_group_separators)
    : TableView(model, columns, table_type, false, resizable_columns,
                autosize_columns),
    model_(model),
    draw_group_separators_(draw_group_separators),
    ALLOW_THIS_IN_INITIALIZER_LIST(sync_selection_factory_(this)) {
}

GroupTableView::~GroupTableView() {
}

void GroupTableView::SyncSelection() {
  int index = 0;
  int row_count = model_->RowCount();
  GroupRange group_range;
  while (index < row_count) {
    model_->GetGroupRangeForItem(index, &group_range);
    if (group_range.length == 1) {
      // No synching required for single items.
      index++;
    }  else {
      // We need to select the group if at least one item is selected.
      bool should_select = false;
      for (int i = group_range.start;
           i < group_range.start + group_range.length; ++i) {
        if (IsItemSelected(i)) {
          should_select = true;
          break;
        }
      }
      if (should_select) {
        for (int i = group_range.start;
             i < group_range.start + group_range.length; ++i) {
          SetSelectedState(i, true);
        }
      }
      index += group_range.length;
    }
  }
}

bool GroupTableView::OnKeyDown(ui::KeyboardCode virtual_keycode) {
  // In a list view, multiple items can be selected but only one item has the
  // focus. This creates a problem when the arrow keys are used for navigating
  // between items in the list view. An example will make this more clear:
  //
  // Suppose we have 5 items in the list view, and three of these items are
  // part of one group:
  //
  // Index0: ItemA (No Group)
  // Index1: ItemB (GroupX)
  // Index2: ItemC (GroupX)
  // Index3: ItemD (GroupX)
  // Index4: ItemE (No Group)
  //
  // When GroupX is selected (say, by clicking on ItemD with the mouse),
  // GroupTableView::SyncSelection() will make sure ItemB, ItemC and ItemD are
  // selected. Also, the item with the focus will be ItemD (simply because
  // this is the item the user happened to click on). If then the UP arrow is
  // pressed once, the focus will be switched to ItemC and not to ItemA and the
  // end result is that we are stuck in GroupX even though the intention was to
  // switch to ItemA.
  //
  // For that exact reason, we need to set the focus appropriately when we
  // detect that one of the arrow keys is pressed. Thus, when it comes time
  // for the list view control to actually switch the focus, the right item
  // will be selected.
  if ((virtual_keycode != ui::VKEY_UP) &&
      (virtual_keycode != ui::VKEY_DOWN)) {
    return TableView::OnKeyDown(virtual_keycode);
  }

  // We start by finding the index of the item with the focus. If no item
  // currently has the focus, then this routine doesn't do anything.
  int focused_index;
  int row_count = model_->RowCount();
  for (focused_index = 0; focused_index < row_count; focused_index++) {
    if (ItemHasTheFocus(focused_index)) {
      break;
    }
  }

  if (focused_index == row_count)
    return false;

  DCHECK_LT(focused_index, row_count);

  // Nothing to do if the item which has the focus is not part of a group.
  GroupRange group_range;
  model_->GetGroupRangeForItem(focused_index, &group_range);
  if (group_range.length == 1)
    return false;

  // If the user pressed the UP key, then the focus should be set to the
  // topmost element in the group. If the user pressed the DOWN key, the focus
  // should be set to the bottommost element.
  if (virtual_keycode == ui::VKEY_UP) {
    SetFocusOnItem(group_range.start);
  } else {
    DCHECK_EQ(virtual_keycode, ui::VKEY_DOWN);
    SetFocusOnItem(group_range.start + group_range.length - 1);
  }

  return false;
}

void GroupTableView::PrepareForSort() {
  GroupRange range;
  int row_count = RowCount();
  model_index_to_range_start_map_.clear();
  for (int model_row = 0; model_row < row_count;) {
    model_->GetGroupRangeForItem(model_row, &range);
    for (int range_counter = 0; range_counter < range.length; range_counter++)
      model_index_to_range_start_map_[range_counter + model_row] = model_row;
    model_row += range.length;
  }
}

int GroupTableView::CompareRows(int model_row1, int model_row2) {
  int range1 = model_index_to_range_start_map_[model_row1];
  int range2 = model_index_to_range_start_map_[model_row2];
  if (range1 == range2) {
    // The two rows are in the same group, sort so that items in the same group
    // always appear in the same order.
    return model_row1 - model_row2;
  }
  // Sort by the first entry of each of the groups.
  return TableView::CompareRows(range1, range2);
}

void GroupTableView::OnSelectedStateChanged() {
  // The goal is to make sure all items for a same group are in a consistent
  // state in term of selection. When a user clicks an item, several selection
  // messages are sent, possibly including unselecting all currently selected
  // items. For that reason, we post a task to be performed later, after all
  // selection messages have been processed. In the meantime we just ignore all
  // selection notifications.
  if (sync_selection_factory_.empty()) {
    MessageLoop::current()->PostTask(FROM_HERE,
        sync_selection_factory_.NewRunnableMethod(
            &GroupTableView::SyncSelection));
  }
  TableView::OnSelectedStateChanged();
}

// Draws the line separator betweens the groups.
void GroupTableView::PostPaint(int model_row, int column, bool selected,
                               const gfx::Rect& bounds, HDC hdc) {
  if (!draw_group_separators_)
    return;

  GroupRange group_range;
  model_->GetGroupRangeForItem(model_row, &group_range);

  // We always paint a vertical line at the end of the last cell.
  HPEN hPen = CreatePen(PS_SOLID, kSeparatorLineThickness, kSeparatorLineColor);
  HPEN hPenOld = (HPEN) SelectObject(hdc, hPen);
  int x = bounds.right() - kSeparatorLineThickness;
  MoveToEx(hdc, x, bounds.y(), NULL);
  LineTo(hdc, x, bounds.bottom());

  // We paint a separator line after the last item of a group.
  if (model_row == (group_range.start + group_range.length - 1)) {
    int y = bounds.bottom() - kSeparatorLineThickness;
    MoveToEx(hdc, 0, y, NULL);
    LineTo(hdc, bounds.width(), y);
  }
  SelectObject(hdc, hPenOld);
  DeleteObject(hPen);
}

std::string GroupTableView::GetClassName() const {
  return kViewClassName;
}

}  // namespace views