// -*- mode: c++; c-file-style: "linux"; c-basic-offset: 2; indent-tabs-mode: nil -*-
//
//  Copyright (C) 2008-2015 Andrej Vodopivec <andrej.vodopivec@gmail.com>
//            (C) 2008-2009 Ziga Lenarcic <zigalenarcic@users.sourceforge.net>
//            (C) 2012 Doug Ilijev <doug.ilijev@gmail.com>
//            (C) 2014-2018 Gunter Königsmann <wxMaxima@physikbuch.de>
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
//  SPDX-License-Identifier: GPL-2.0+

/*! \file
  This file defines the class GroupCell

  GroupCell is the Cell type that bundles user input with eventual images or 
  output from maxima that belongs to it.
*/

#include "GroupCell.h"

#include "CellImpl.h"
#include "CellList.h"
#include "CellPointers.h"
#include "ImgCell.h"
#include "MarkDown.h"
#include "SlideShowCell.h"
#include "TextCell.h"
#include "LabelCell.h"
#include "stx/unique_cast.hpp"
#include <wx/config.h>
#include <wx/clipbrd.h>

#ifdef __WINDOWS__
constexpr bool TEMPORARY_WINDOWS_PERFORMANCE_HACK = true;
#else
constexpr bool TEMPORARY_WINDOWS_PERFORMANCE_HACK = false;
#endif

#if wxUSE_ACCESSIBILITY
  // TODO This class is not used anywhere.
  class HCaretCell: public wxAccessible
  {
  public:
    explicit HCaretCell(GroupCell* group) : wxAccessible(), m_group(group) {}

    //! Describe the current cell to a Screen Reader
    wxAccStatus GetDescription(int WXUNUSED(childId), wxString *description) override
    {
      if (description)
        return (*description = _("A space between GroupCells")), wxACC_OK;

      return wxACC_FAIL;
    }
    //! Inform the Screen Reader which cell is the parent of this one
    wxAccStatus GetParent (wxAccessible ** parent) override
    {
      if (parent)
        return (*parent = m_group->GetAccessible()), wxACC_OK;

      return wxACC_FAIL;
    }
    //! How many childs of this cell GetChild() can retrieve?
    wxAccStatus GetChildCount (int *childCount) override
    {
      if (childCount)
        return (*childCount = 0), wxACC_OK;

      return wxACC_FAIL;
    }
    //! Retrieve a child cell. childId=0 is the current cell
    wxAccStatus GetChild (int childId, wxAccessible **child) override
    {
      if (childId == 0 && child)
        return (*child = this), wxACC_OK;

      return wxACC_FAIL;
    }
    // //! Does this or a child cell currently own the focus?
    // wxAccStatus GetFocus (int *childId, wxAccessible **child)
    //   {
    //   }
    // //! Where is this cell to be found?
    // wxAccStatus GetLocation (wxRect &rect, int elementId)
    //   {
    //   }
    // //! Is pt inside this cell or a child cell?
    // wxAccStatus HitTest (const wxPoint &pt,
    //                      int *childId, wxAccessible **childObject);
    wxAccStatus GetRole (int childId, wxAccRole *role) override;

  private:
	GroupCell *m_group;
  };
#endif


#define EMPTY_INPUT_LABEL wxT(" -->  ")

GroupCell::GroupCell(Configuration **config, GroupType groupType, const wxString &initString) :
    Cell(this, config),
    m_groupType(groupType)
{
  InitBitFields();
  m_mathFontSize = (*m_configuration)->GetMathFontSize();
  ForceBreakLine();
  m_type = MC_TYPE_GROUP;

  // set up cell depending on groupType, so we have a working cell
  if (groupType != GC_TYPE_PAGEBREAK)
  {
    if (groupType == GC_TYPE_CODE)
      m_inputLabel = std::make_unique<LabelCell>(this, m_configuration, EMPTY_INPUT_LABEL);
    else
      m_inputLabel = std::make_unique<TextCell>(this, m_configuration, wxT(""));

    m_inputLabel->SetType(MC_TYPE_MAIN_PROMPT);
  }

  std::unique_ptr<EditorCell> editor;
  switch (groupType)
  {
    case GC_TYPE_CODE:
      editor = std::make_unique<EditorCell>(this, m_configuration);
      editor->SetType(MC_TYPE_INPUT);
      break;
    case GC_TYPE_TEXT:
      m_inputLabel->SetType(MC_TYPE_TEXT);
      editor = std::make_unique<EditorCell>(this, m_configuration);
      editor->SetType(MC_TYPE_TEXT);
      break;
    case GC_TYPE_TITLE:
      m_inputLabel->SetType(MC_TYPE_TITLE);
      editor = std::make_unique<EditorCell>(this, m_configuration);
      editor->SetType(MC_TYPE_TITLE);
      break;
    case GC_TYPE_SECTION:
      m_inputLabel->SetType(MC_TYPE_SECTION);
      editor = std::make_unique<EditorCell>(this, m_configuration);
      editor->SetType(MC_TYPE_SECTION);
      break;
    case GC_TYPE_SUBSECTION:
      m_inputLabel->SetType(MC_TYPE_SUBSECTION);
      editor = std::make_unique<EditorCell>(this, m_configuration);
      editor->SetType(MC_TYPE_SUBSECTION);
      break;
    case GC_TYPE_SUBSUBSECTION:
      m_inputLabel->SetType(MC_TYPE_SUBSUBSECTION);
      editor = std::make_unique<EditorCell>(this, m_configuration);
      editor->SetType(MC_TYPE_SUBSUBSECTION);
      break;
    case GC_TYPE_HEADING5:
      m_inputLabel->SetType(MC_TYPE_HEADING5);
      editor = std::make_unique<EditorCell>(this, m_configuration);
      editor->SetType(MC_TYPE_HEADING5);
      break;
    case GC_TYPE_HEADING6:
      m_inputLabel->SetType(MC_TYPE_HEADING6);
      editor = std::make_unique<EditorCell>(this, m_configuration);
      editor->SetType(MC_TYPE_HEADING6);
      break;
    case GC_TYPE_IMAGE:
      m_inputLabel->SetType(MC_TYPE_IMAGE);
      editor = std::make_unique<EditorCell>(this, m_configuration);
      editor->SetType(MC_TYPE_IMAGE);
      break;
    default:
      break;
  }

  if (editor && !initString.empty())
    editor->SetValue(initString);
  if (editor)
    AppendInput(std::move(editor));

  // when creating an image cell, if a string is provided
  // it loads an image (without deleting it)
  if ((groupType == GC_TYPE_IMAGE) && (initString.Length() > 0))
  {
    std::unique_ptr<Cell> ic;
    if (wxImage::GetImageCount(initString) < 2)
      ic = std::make_unique<ImgCell>(this, m_configuration, initString, std::shared_ptr<wxFileSystem>{} /* system fs */, false);
    else
      ic = std::make_unique<SlideShow>(this, m_configuration, initString, false);
    AppendOutput(std::move(ic));
  }
}

GroupCell::GroupCell(const GroupCell &cell):
    GroupCell(cell.m_configuration, cell.m_groupType)
{
  CopyCommonData(cell);
  if (cell.m_inputLabel)
    SetInput(cell.m_inputLabel->CopyList());
  if (cell.m_output)
    SetOutput(cell.m_output->CopyList());
  SetAutoAnswer(cell.m_autoAnswer);
  UpdateYPosition();
}

DEFINE_CELL(GroupCell)

std::unique_ptr<GroupCell> GroupCell::CopyList() const
{
  auto copy = Cell::CopyList();
  return stx::static_unique_ptr_cast<GroupCell>(std::move(copy));
}

bool GroupCell::Empty() const
{
  return (
          // No next cell
          (!GetNext()) &&
          // This cell at maximum contains a prompt.
          (ToString().Length() < 6)
  );
}

void GroupCell::ResetInputLabel()
{
  if (m_groupType == GC_TYPE_CODE)
  {
    if (m_inputLabel)
      m_inputLabel->SetValue(EMPTY_INPUT_LABEL);
  }
}

void GroupCell::ResetInputLabelList()
{
  for (auto &tmp : OnList(this))
  {
    tmp.ResetInputLabel();
    // also reset input labels in the folded cells
    if (tmp.IsFoldable() && (tmp.m_hiddenTree))
      tmp.m_hiddenTree->ResetInputLabelList();
  }
}

bool GroupCell::IsFoldable() const
{
  return ((m_groupType == GC_TYPE_SECTION) ||
          (m_groupType == GC_TYPE_TITLE) ||
          (m_groupType == GC_TYPE_SUBSECTION) ||
          (m_groupType == GC_TYPE_SUBSUBSECTION) ||
          (m_groupType == GC_TYPE_HEADING5) ||
          (m_groupType == GC_TYPE_HEADING6)
          );
}

GroupCell::~GroupCell()
{}

const wxString &GroupCell::GetAnswer(int answer) const
{
  if((!m_autoAnswer) && (!(*m_configuration)->OfferKnownAnswers()))
    return wxm::emptyString;

  wxString const question = wxString::Format(wxT("Question #%i"),answer);
  auto it = m_knownAnswers.find(question);
  return (it != m_knownAnswers.end()) ? it->second : wxm::emptyString;
}

const wxString &GroupCell::GetAnswer(const wxString &question) const
{
  if((!m_autoAnswer) && (!(*m_configuration)->OfferKnownAnswers()))
    return wxm::emptyString;

  auto it = m_knownAnswers.find(question);
  return (it != m_knownAnswers.end()) ? it->second : GetAnswer(++m_numberedAnswersCount);
}

void GroupCell::SetAutoAnswer(bool autoAnswer)
{
  m_autoAnswer = autoAnswer;
  if (GetEditable()) GetEditable()->AutoAnswer(autoAnswer);
}

void GroupCell::SetAnswer(const wxString &question, const wxString &answer)
{
  if (!answer.empty())
    m_knownAnswers[question] = answer;
}

GroupCell *GroupCell::GetLastWorkingGroup() const
{ return m_cellPointers->m_lastWorkingGroup; }

wxString GroupCell::TexEscapeOutputCell(wxString Input)
{
  wxString retval(Input);
  Input.Replace(wxT("#"), wxT("\\#"));
  return (Input);
}

void GroupCell::SetInput(std::unique_ptr<Cell> &&input)
{
  if (!input)
    return;
  m_inputLabel = std::move(input);
  m_updateConfusableCharWarnings = true;
  InputHeightChanged();
}

void GroupCell::AppendInput(std::unique_ptr<Cell> &&cell)
{
  if (!m_inputLabel)
  {
    m_inputLabel = std::move(cell);
  }
  else
  {
    if (m_inputLabel->GetNext() == NULL)
      CellList::AppendCell(m_inputLabel, std::move(cell));
    else if (m_inputLabel->GetNext()->GetValue().Length() == 0)
    {
      // AppendCell is needed due to its side effect of doing
      // m_group->ResetData. Perhaps we can decide that SetNext alone could do
      // something like that, as long as it wouldn't cause quadratic behavior.
      CellList::SetNext(m_inputLabel.get(), nullptr);
      wxASSERT(!m_inputLabel->GetNextToDraw());
      CellList::AppendCell(m_inputLabel, std::move(cell));
    }
    else
    {
      AppendOutput(std::move(cell));
      Cell::Hide(false);
    }
  }
  m_updateConfusableCharWarnings = true;
  InputHeightChanged();
}


void GroupCell::SetOutput(std::unique_ptr<Cell> &&output)
{
  if((m_cellPointers->m_answerCell) &&(m_cellPointers->m_answerCell->GetGroup() == this))
    m_cellPointers->m_answerCell = nullptr;
  
  m_output.reset();
  AppendOutput(std::move(output));
}

void GroupCell::RemoveOutput()
{
  m_numberedAnswersCount = 0;
  if (m_output == NULL)
    return;
  // If there is nothing to do we can skip the rest of this action.

  if((m_cellPointers->m_answerCell) &&(m_cellPointers->m_answerCell->GetGroup() == this))
    m_cellPointers->m_answerCell = nullptr;

  if (GetGroupType() != GC_TYPE_IMAGE)
    m_output.reset();

  m_cellPointers->m_errorList.Remove(this);
  // Calculate the new cell height.

  Cell::Hide(false);

  m_height = m_inputHeight;
  if(m_inputLabel)
    m_width = m_inputLabel->GetFullWidth();
  else
    m_width = 50;
  (*m_configuration)->AdjustWorksheetSize(true);
  
  // Move all cells that follow the current one up by the amount this cell has shrinked.
  UpdateCellsInGroup();

  m_updateConfusableCharWarnings = true;
  ResetData();
}

void GroupCell::AppendOutput(std::unique_ptr<Cell> &&cell)
{
  wxASSERT_MSG(cell, _("Bug: Trying to append NULL to a group cell."));
  if (!cell) return;
  if (!m_output)
  {
    m_output = std::move(cell);

    auto *input = GetEditable();
    if (m_groupType == GC_TYPE_CODE && input)
      input->ContainsChanges(false);
  }
  else
  {
    CellList::AppendCell(m_output, std::move(cell));
  }
  UpdateCellsInGroup();
  m_updateConfusableCharWarnings = true;
  ResetSize();

  // IMPORTANT! Do not Recalculate() here, as it is quadratic behavior
  // and it makes interactive performance go down the drain. If there are
  // visual glitches that a recalculation here would "fix", they must be
  // fixed at their source. Doing it by recalculating here will only patch
  // around the problem and make wxMaxima perform like molasses. IMPORTANT!
  if (TEMPORARY_WINDOWS_PERFORMANCE_HACK)
    Recalculate(); // FIXME this has to go away eventually
}

WX_DECLARE_STRING_HASH_MAP(int, CmdsAndVariables);

void GroupCell::UpdateConfusableCharWarnings()
{
  ClearToolTip();

  wxString output;
  if (GetOutput())
    output += GetOutput()->VariablesAndFunctionsList();
  // Extract all variable and command names from the cell including input and output
  CmdsAndVariables cmdsAndVariables;

  if (GetEditable())
    for (auto const &tok : MaximaTokenizer(
           output, *m_configuration, GetEditable()->GetTokens()).PopTokens())
      if((tok.GetStyle() == TS_CODE_VARIABLE) || (tok.GetStyle() == TS_CODE_FUNCTION))
        cmdsAndVariables[tok.GetText()] = 1;
  
  // Now we step through all the words we found
  while(!cmdsAndVariables.empty())
  {
    CmdsAndVariables::iterator cmp = cmdsAndVariables.begin();
    wxString word;
    word = cmp->first;
    cmdsAndVariables.erase(cmp);
    // Now iterate through all remaining words
    cmp = cmdsAndVariables.begin();
    while(cmp != cmdsAndVariables.end())
    {
      // iterate through all lookalike chars
      for (wxString::const_iterator it = m_lookalikeChars.begin(); it != m_lookalikeChars.end(); ++it)
      {
        wxUniChar ch1 = *it++;
        wxASSERT(it != m_lookalikeChars.end());
        wxUniChar ch2 = *it;
        wxString word_subst = word;
        if(word_subst.Replace(ch1,ch2))
        {
          if(cmp->first == word_subst)
            AddToolTip(_("Warning: Lookalike chars: ") +
                       cmp->first.utf8_str() + wxT("\u2260") +
                       word.utf8_str()
              );
        }
        word_subst = word;
        if(word_subst.Replace(ch2,ch1))
        {
          if(cmp->first == word_subst)
            AddToolTip(_("Warning: Lookalike chars: ") +
                       cmp->first +
                       wxT(" \u2260 ") +
                       word
              );
        }
          
      }
      ++cmp;
    }
  }
  m_updateConfusableCharWarnings = false;
}

void GroupCell::Recalculate()
{
  if (NeedsRecalculation(EditorFontSize()))
  {
    Cell::Recalculate((*m_configuration)->GetDefaultFontSize());
    m_mathFontSize = (*m_configuration)->GetMathFontSize();
    Configuration *configuration = (*m_configuration);
    ClearNeedsToRecalculateWidths();

    // Recalculating pagebreaks is simple
    if (m_groupType == GC_TYPE_PAGEBREAK)
    {
      m_width = configuration->GetCellBracketWidth();
      m_height = 2;
      m_center = 1;
      return;
    }
    
    if(m_inputLabel != NULL)
      RecalculateInput();

    RecalculateOutput();
  }
  // The line breaking will have set our "needs recalculation" flag again.
  UpdateYPosition();
  Cell::Recalculate((*m_configuration)->GetDefaultFontSize(
                      ));
  m_clientWidth_old = (*m_configuration)->GetClientWidth();
}

void GroupCell::InputHeightChanged()
{
  ResetCellListSizes();
  if(m_inputLabel)
    m_inputLabel->ResetCellListSizes();
  EditorCell *editorCell = GetEditable();
  if(editorCell == NULL)
    return;
  RecalculateInput();
  if(m_output != NULL)
  {
    m_height += m_outputRect.GetHeight();
    m_outputRect.y = m_currentPoint.y + m_center;
    m_width = wxMax(m_width, m_output->GetLineWidth());
  }
  UpdateYPositionList();
}

// Called on resize events
// We need to forget line breaks/breakup cells and
// breakup cells and compute new line breaks
void GroupCell::OnSize()
{
  InputHeightChanged();
  BreakLines();
}

AFontSize GroupCell::EditorFontSize() const
{
  Configuration *configuration = (*m_configuration);
  AFontSize fontSize = configuration->GetDefaultFontSize();
  if(GetEditable())
  {
    fontSize = configuration->GetFontSize(GetEditable()->GetStyle());
    if (fontSize.IsNull())
      fontSize = configuration->GetDefaultFontSize();
  }
  return fontSize;
}

void GroupCell::RecalculateInput()
{
  Configuration *configuration = (*m_configuration);
  m_currentPoint.x = configuration->GetIndent();
  
  if (m_inputLabel)
    m_inputLabel->SetCurrentPoint(m_currentPoint);
  if (GetEditable())
  {
    wxPoint in = GetCurrentPoint();
    
    in.x += GetInputIndent();
    GetEditable()->SetCurrentPoint(in);
  }
  
  // special case
  if (m_groupType == GC_TYPE_PAGEBREAK)
  {
    m_inputWidth = m_width = configuration->GetCellBracketWidth();
    m_inputHeight = m_height = 2;
    m_center = 0;
    return;
  }
  else
  {
    SetZeroSize();
    
    if ((configuration->ShowCodeCells()) ||
        (m_groupType != GC_TYPE_CODE))
    {
      if(GetEditable())
        GetEditable()->RecalculateList(EditorFontSize());
      
      if(m_inputLabel)
      {
        m_inputLabel->Recalculate(EditorFontSize());
        m_inputWidth = m_width = m_inputLabel->GetFullWidth();
        m_center = m_inputLabel->GetCenterList();
        m_inputHeight = m_height = m_inputLabel->GetHeightList();
      }
    }
  }
  m_height = m_inputHeight;
  if(!m_inputLabel)
    m_center = m_height;
  m_width = m_inputWidth;
}

void GroupCell::RecalculateOutput()
{
  m_outputRect = wxRect(m_currentPoint.x, m_currentPoint.y + m_center,
                        0, 0);
  if(IsHidden())
    return;

  if(m_output == NULL)
    return;
  
  Configuration *configuration = (*m_configuration);
    
  m_mathFontSize = (*m_configuration)->GetMathFontSize();

  // The following line is a hack, kind of: Without it the first
  // (and only) line of an image that was included using the gui, not maxima
  // (and that therefore doesn't start in a label that per definition breaks
  // a line) later will not trigger the
  //  if (tmp.BreakLineHere())
  // that causes its height to be calculated.
  m_output->ForceBreakLine();

  m_mathFontSize = configuration->GetMathFontSize(); //-V519
  
  // Recalculate size of all output cells
  for (Cell &tmp : OnList(m_output.get()))
  {
    tmp.Recalculate(tmp.IsMath() ?
                        (*m_configuration)->GetMathFontSize() :
                        (*m_configuration)->GetDefaultFontSize());
  }
  
  // Breakup cells and break lines
  BreakLines();
  
  // Recalculate size of cells again: Their size might have changed during breaking
  // lines
  for (Cell &tmp : OnList(m_output.get()))
  {
    tmp.Recalculate(tmp.IsMath() ?
                    (*m_configuration)->GetMathFontSize() :
                    (*m_configuration)->GetDefaultFontSize());
  }

  // Calculate the height of the output
  for (Cell &tmp : OnDrawList(m_output.get()))
  {
    if (tmp.BreakLineHere())
    {
      tmp.ResetCellListSizes();
      int height_Delta = tmp.GetHeightList();
      m_width = wxMax(m_width, tmp.GetLineWidth());
      m_outputRect.width = wxMax(m_outputRect.width, m_width);
      m_outputRect.height += height_Delta;
      
      if (tmp.GetPrevious() &&
          ((tmp.GetStyle() == TS_LABEL) || (tmp.GetStyle() == TS_USERLABEL)))
        m_outputRect.height += configuration->GetInterEquationSkip();

      if (tmp.HasBigSkip())
        m_outputRect.height += MC_LINE_SKIP;
    }
  }

  m_height = m_outputRect.GetHeight() + m_inputHeight;
  // Move all cells that follow the current one down by the amount this cell has grown.
  (*m_configuration)->AdjustWorksheetSize(true);
}

bool GroupCell::NeedsRecalculation(AFontSize fontSize) const
{
  return Cell::NeedsRecalculation(fontSize) ||
    (GetEditable() && GetEditable()->NeedsRecalculation(EditorFontSize())) ||
    (m_clientWidth_old != (*m_configuration)->GetClientWidth());
}

void GroupCell::UpdateYPositionList()
{
  for (auto &tmp : OnList(this))
    tmp.UpdateYPosition();
}

void GroupCell::UpdateYPosition()
{
  auto *const configuration = (*m_configuration);
  auto *const previous = GetPrevious();
  
  wxPoint point(configuration->GetIndent(), GetCenter());
  if (!previous)
  {
    point.y += configuration->GetBaseIndent();
    if (m_inputLabel)
      m_inputLabel->SetCurrentPoint(point);
  }
  else
  {
    point.y += configuration->GetGroupSkip();
    if (previous->GetCurrentPoint().y > 0)
      point.y += previous->GetCurrentPoint().y +
                 previous->GetMaxDrop();
  }
  m_currentPoint = point;
}

int GroupCell::GetInputIndent()
{
  int labelWidth = 0;
  if((*m_configuration)->IndentMaths())
    labelWidth = Scale_Px((*m_configuration)->GetLabelWidth()) + MC_TEXT_PADDING;
  
  if(m_inputLabel != NULL)
  {
    if(m_inputLabel->GetWidth() >= 0)
      labelWidth = wxMax(m_inputLabel->GetWidth() + MC_TEXT_PADDING,labelWidth);
    else
      labelWidth = wxMax(m_labelWidth_cached,labelWidth);
  }
  
  m_labelWidth_cached = labelWidth;
    
  return labelWidth;
}

void GroupCell::Draw(wxPoint const point)
{
  Cell::Draw(point);
  Configuration *configuration = (*m_configuration);

  if (configuration->ShowBrackets())
    DrawBracket();

  if (!DrawThisCell(point))
    return;

  if (!TEMPORARY_WINDOWS_PERFORMANCE_HACK)
  {
    if (NeedsRecalculation((*m_configuration)->GetDefaultFontSize()))
      Recalculate();
  }
  if (m_updateConfusableCharWarnings)
    UpdateConfusableCharWarnings();

  wxDC *dc = configuration->GetDC();
  // draw a thick line for 'page break'
  // and return
  if (m_groupType == GC_TYPE_PAGEBREAK)
  {
    wxRect rect = GetRect(false);
    int y = rect.GetY();
    wxPen pen(configuration->GetColor(TS_CURSOR), 1, wxPENSTYLE_DOT);
    dc->SetPen(pen);
    dc->DrawLine(0, y, (*m_configuration)->GetCanvasSize().GetWidth(), y);
    return;
  }

  wxRect rect = GetRect(false);

  if(configuration->GetIndent() < rect.GetRight())
  {
    if(rect.GetLeft() <= configuration->GetCellBracketWidth())
      rect.SetLeft(configuration->GetIndent());

    //
    // Draw input and output
    //
    SetPen();

    if (m_output && !IsHidden())
    {
      wxPoint in = point;
      if (configuration->ShowCodeCells() || (m_groupType != GC_TYPE_CODE))
        in.y += m_inputLabel->GetMaxDrop();

      m_outputRect.SetPosition(in);

      bool first = true;
      int drop = 0;
      for (Cell &tmp : OnDrawList(m_output.get()))
      {
        if (first || tmp.BreakLineHere())
        {
          if (!first && tmp.HasBigSkip())
            in.y += MC_LINE_SKIP;

          in.x = point.x + GetLineIndent(&tmp);
          in.y += drop + tmp.GetCenterList();
          drop = tmp.GetMaxDrop();
        }

        tmp.Draw(in);
        in.x += tmp.GetWidth();
        first = false;
      }
    }
    if ((configuration->ShowCodeCells()) || (m_groupType != GC_TYPE_CODE))
    {
      configuration->Outdated(false);

      EditorCell *input = GetEditable();
      if (input)
        input->Draw({point.x + GetInputIndent(), point.y});

      if (GetPrompt())
        GetPrompt()->Draw(point);

      if (m_groupType == GC_TYPE_CODE && input)
        configuration->Outdated(input->ContainsChanges());
    }
  }
  configuration->Outdated(false);
}

bool GroupCell::AddEnding()
{
  return GetEditable() && GetEditable()->AddEnding();
}

wxRect GroupCell::GetRect(bool WXUNUSED(all)) const
{
  return wxRect(m_currentPoint.x, m_currentPoint.y - m_center,
                m_width, m_height);
}

int GroupCell::GetLineIndent(Cell *cell)
{
  if (cell &&
      (cell->GetStyle() != TS_LABEL) &&
      (cell->GetStyle() != TS_USERLABEL) &&
      (cell->GetStyle() != TS_MAIN_PROMPT) &&
      (cell->GetStyle() != TS_OTHER_PROMPT) &&
      (cell->GetStyle() != TS_ASCIIMATHS) &&
      (*m_configuration)->IndentMaths())
    return Scale_Px((*m_configuration)->GetLabelWidth()) + 2 * MC_TEXT_PADDING;
  return 0;
}

void GroupCell::UpdateCellsInGroup()
{
  if(m_output != NULL)
    m_cellsInGroup = 2 + m_output->CellsInListRecursive();
  else
    m_cellsInGroup = 2;
}

void GroupCell::CellUnderPointer(GroupCell *cell)
{
  m_cellPointers->m_groupCellUnderPointer = cell;
}

void GroupCell::DrawBracket()
{
  // If the current cell doesn't know where it is on the screen we don't
  // attempt to draw it's bracket.
  if((GetRect().GetLeft() < 0) || (GetRect().GetTop() < 0))
    return;

  Configuration *configuration = (*m_configuration);
  bool drawBracket = !configuration->HideBrackets();

  if (this == m_cellPointers->m_groupCellUnderPointer)
    drawBracket = true;

  wxDC *dc = configuration->GetDC();
  wxDC *adc = configuration->GetAntialiassingDC();

  int selectionStart_px = -1;
  if (m_cellPointers->m_selectionStart &&
      (m_cellPointers->m_selectionStart->GetType() == MC_TYPE_GROUP))
    selectionStart_px = m_cellPointers->m_selectionStart.CastAs<GroupCell *>()->m_currentPoint.y;

  int selectionEnd_px = -1;
  if (m_cellPointers->m_selectionEnd &&
      (m_cellPointers->m_selectionEnd->GetType() == MC_TYPE_GROUP))
    selectionEnd_px = m_cellPointers->m_selectionEnd.CastAs<GroupCell *>()->m_currentPoint.y;
  
  // Mark this GroupCell as selected if it is selected. Else clear the space we
  // will add brackets in
  if ((m_currentPoint.y >= selectionStart_px) &&
      (m_currentPoint.y <= selectionEnd_px))
  {
    dc->SetPen(*(wxThePenList->FindOrCreatePen(
                  configuration->GetColor(TS_SELECTION),
                  configuration->GetDefaultLineWidth(),
                  wxPENSTYLE_SOLID)
                ));
// window linux, set a pen
    dc->SetBrush(*(wxTheBrushList->FindOrCreateBrush(configuration->GetColor(TS_SELECTION))));
    drawBracket = true;
  }
  else if (m_cellPointers->m_errorList.Contains(this))
  {
    dc->SetPen(*wxRED_PEN);
    dc->SetBrush(*wxRED_BRUSH);
    drawBracket = true;
  }
  else
  {
    if ((m_cellPointers->m_answerCell) && (m_cellPointers->m_answerCell->GetGroup() == this))
    {
      dc->SetPen(*wxYELLOW_PEN);
      dc->SetBrush(*wxYELLOW_BRUSH);
      drawBracket = true;
    }
    else
    {
      dc->SetBrush((*m_configuration)->GetBackgroundBrush());
      dc->SetPen(*(wxThePenList->FindOrCreatePen(
                     configuration->DefaultBackgroundColor(),
                     configuration->GetDefaultLineWidth(),
                     wxPENSTYLE_SOLID)
                   ));
    }
  }
  wxRect rect = GetRect();
  rect = wxRect(
          configuration->GetIndent() - configuration->GetCellBracketWidth(),
          rect.GetTop() - 2,
          configuration->GetCellBracketWidth(),
          rect.GetHeight() + 5
  );
  if (configuration->InUpdateRegion(rect))
    dc->DrawRectangle(Cell::CropToUpdateRegion(rect));

  //
  // Mark groupcells currently in queue.
  //
  if (m_inEvaluationQueue)
  {
    drawBracket = true;
    dc->SetBrush(*wxTRANSPARENT_BRUSH);
    if (m_lastInEvaluationQueue)
      dc->SetPen(*(wxThePenList->FindOrCreatePen(
              configuration->GetColor(TS_CELL_BRACKET),
              2 * configuration->GetDefaultLineWidth(),
              wxPENSTYLE_SOLID)));
    else
      dc->SetPen(*(wxThePenList->FindOrCreatePen(configuration->GetColor(TS_CELL_BRACKET),
                                                configuration->GetDefaultLineWidth(),
                                                wxPENSTYLE_SOLID)));

    wxRect bracketRect = wxRect(
      configuration->GetIndent() - configuration->GetCellBracketWidth(),
      rect.GetTop() - 2,
      configuration->GetCellBracketWidth(),
      rect.GetHeight() + 5);
    if (configuration->InUpdateRegion(bracketRect))
      dc->DrawRectangle(bracketRect);
  }

  Cell *editable = GetEditable();
  if (editable != NULL && editable->IsActive())
  {
    drawBracket = true;
    adc->SetPen(*(wxThePenList->FindOrCreatePen(configuration->GetColor(TS_ACTIVE_CELL_BRACKET),
                                              2 * configuration->GetDefaultLineWidth(),
                                              wxPENSTYLE_SOLID))); // window linux, set a pen
    dc->SetBrush(*(wxTheBrushList->FindOrCreateBrush(configuration->GetColor(TS_ACTIVE_CELL_BRACKET)))); //highlight c.
  }
  else
  {
    adc->SetPen(*(wxThePenList->FindOrCreatePen(configuration->GetColor(TS_CELL_BRACKET),
                                              configuration->GetDefaultLineWidth(),
                                              wxPENSTYLE_SOLID))); // window linux, set a pen
    dc->SetBrush(*(wxTheBrushList->FindOrCreateBrush(configuration->GetColor(TS_CELL_BRACKET)))); //highlight c.
  }

  if ((!IsHidden()) && (!m_hiddenTree))
  {
    dc->SetBrush(*wxTRANSPARENT_BRUSH);
  }

  if (drawBracket)
  {
    adc->SetBrush(dc->GetBrush());
    SetPen(1.5);
    int bracketWidth = configuration->GetCellBracketWidth() - configuration->GetDefaultLineWidth();
    int lineWidth = configuration->GetDefaultLineWidth();
    int lineWidth_2 = configuration->GetDefaultLineWidth() / 2.0;
    if (IsFoldable())
    { // draw the square that allows hiding and unhiding the cell
      const wxPoint points[4] = {
        {-bracketWidth, 0},
        {-bracketWidth, bracketWidth},
        {-lineWidth,    bracketWidth},
        {-lineWidth,    0}
      };
      adc->DrawPolygon(4, points, m_currentPoint.x, m_currentPoint.y - m_center);
    }
    else
    {
      int n = 0;
      wxPoint points[9];
      // draw the triangle that allows hiding and unhiding the cell
      points[n++] = {-bracketWidth + lineWidth_2,  bracketWidth - lineWidth_2};
      points[n++] = {-lineWidth,                   lineWidth_2};
      points[n++] = {-bracketWidth + lineWidth_2,  lineWidth_2};

      // The rest of the bracket
      if (configuration->ShowCodeCells() && m_groupType == GC_TYPE_CODE && m_output && !IsHidden())
      {
        points[n++] = {-bracketWidth + lineWidth_2,      m_inputLabel->GetHeightList()};
        points[n++] = {-bracketWidth / 2 + lineWidth_2,  m_inputLabel->GetHeightList()};
        points[n++] = {-bracketWidth + lineWidth_2,      m_inputLabel->GetHeightList()};
      }
      
      // The remaining part of the vertical line at the back
      points[n++] = {-bracketWidth + lineWidth_2,  m_height - lineWidth};
      // The horizontal line at the bottom
      points[n++] = {-lineWidth,                   m_height - lineWidth};
      points[n++] = {-bracketWidth + lineWidth_2,  m_height - lineWidth};

      wxASSERT(n <= (std::end(points) - std::begin(points)));
      adc->DrawPolygon(n, points, m_currentPoint.x, m_currentPoint.y - m_center);
    }
  }
}

wxRect GroupCell::HideRect()
{
  Configuration *configuration = (*m_configuration);
  return wxRect(m_currentPoint.x - configuration->GetCellBracketWidth() - configuration->GetDefaultLineWidth() / 2,
                m_currentPoint.y - m_center - configuration->GetDefaultLineWidth() / 2,
                configuration->GetCellBracketWidth() + configuration->GetDefaultLineWidth(),
                configuration->GetCellBracketWidth() + configuration->GetDefaultLineWidth()
  );
}

wxString GroupCell::ToString() const
{
  wxString str;
  Configuration *configuration = (*m_configuration);

  if (m_inputLabel != NULL)
  {
    if ((configuration->ShowCodeCells()) ||
        (m_groupType != GC_TYPE_CODE))
    {
      str = m_inputLabel->ToString();

      if (GetEditable() != NULL)
        str += GetEditable()->ToString();

      str.Replace(wxT("\n"), wxT("\n\t"));
    }
  }

  if (!IsHidden())
  {
    bool firstCell = true;
    for (Cell &tmp : OnDrawList(m_output.get()))
    {
      if (firstCell || (tmp.HasHardLineBreak() && !str.empty()))
          str += wxT("\n");
      str += tmp.ToString();
      firstCell = false;
    }
  }
  return str;
}

wxString GroupCell::ToTeX() const
{
  return ToTeX(wxEmptyString, wxEmptyString, NULL);
}

wxString GroupCell::ToRTF() const
{
  Configuration *configuration = (*m_configuration);
  if (m_groupType == GC_TYPE_PAGEBREAK)
    return (wxT("\\page "));

  wxString retval;
  if (m_groupType == GC_TYPE_CODE)
  {
    if (m_inputLabel && configuration->ShowCodeCells())
    {
      if (GetPrevious())
        retval = wxT("\\par}{\\pard\\s22\\li1105\\lin1105\\fi-1105\\f0\\fs24 \n");
      else
        retval += wxT("\\pard\\s22\\li1105\\lin1105\\fi-1105\\f0\\fs24 ");
      retval += RTFescape(m_inputLabel->ToString());
      retval += wxT("\\tab\n");
    }
    else
    {
      if (GetPrevious())
        retval = wxT("\\par}\n{\\pard\\s21\\li1105\\lin1105\\f0\\fs24 ");
      else
        retval = wxT("\\pard\\s21\\li1105\\lin1105\\f0\\fs24 ");
    }
  }
  else
    retval = wxT("\\par}\n{");

  if (GetEditable() != NULL)
    retval += GetEditable()->ToRTF();

  Cell *out = GetLabel();
  if (out != NULL)
  {
    retval += out->ListToRTF(true);
  }
  return retval;
}

wxString GroupCell::ToTeX(wxString imgDir, wxString filename, int *imgCounter) const
{
  wxASSERT_MSG((imgCounter != NULL), _(wxT("Bug: No image counter to write to!")));
  if (imgCounter == NULL) return wxEmptyString;
  wxString str;
  switch (m_groupType)
  {
    case GC_TYPE_PAGEBREAK:
      str = wxT("\\pagebreak\n");
      break;

    case GC_TYPE_IMAGE:
      if (imgDir != wxEmptyString)
      {
        auto const copy = m_output->Copy();
        auto *const imgCopy = dynamic_cast<ImgCell *>(copy.get());
        (*imgCounter)++;
        wxString image = filename + wxString::Format(wxT("_%d"), *imgCounter);
        wxString file = imgDir + wxT("/") + image + wxT(".") + imgCopy->GetExtension();

        if (!wxDirExists(imgDir))
          wxMkdir(imgDir);

        if (imgCopy->ToImageFile(file).x >= 0)
        {
          str << wxT("\\begin{figure}[htb]\n")
              << wxT("  \\centering\n")
              << wxT("    \\includeimage{")
              << filename << wxT("_img/") << image << wxT("}\n")
              << wxT("  \\caption{") << m_inputLabel->GetNext()->ToTeX().Trim() << wxT("}\n")
              << wxT("\\end{figure}\n");
        }
      }
      else
        str << wxT("\n\\verb|<<GRAPHICS>>|\n");
      break;

    case GC_TYPE_CODE:
      str = ToTeXCodeCell(imgDir, filename, imgCounter);
      str.Replace(wxT("\\[\\displaystyle \\]"),wxT(""));
      break;

    default:
      if (GetEditable() != NULL && !IsHidden())
      {
        str = GetEditable()->ListToTeX();
        str.Trim(true);
        switch (GetEditable()->GetStyle())
        {
          case TS_TITLE:
            str = wxT("\n\\pagebreak{}\n{\\Huge {\\scshape ") + str + wxT("}}\n");
            str += wxT("\\setcounter{section}{0}\n\\setcounter{subsection}{0}\n");
            str += wxT("\\setcounter{figure}{0}\n");
            break;
          case TS_SECTION:
            // Trim() strings for TeX export to remove newlines in \section{}, \subsection{}, ... commands
	    // LaTeX creates an error on the following code:
	    // \section{Chapter 1
	    //
	    // }
            str = wxT("\n\\section{") + str.Trim() + wxT("}");
            break;
          case TS_SUBSECTION:
            str = wxT("\n\\subsection{") + str.Trim() + wxT("}");
            break;
          case TS_SUBSUBSECTION:
            str = wxT("\n\\subsubsection{") + str.Trim() + wxT("}");
            break;
          case TS_HEADING5:
            str = wxT("\n\\paragraph{") + str.Trim() + wxT("}");
            break;
          case TS_HEADING6:
            str = wxT("\n\\subparagraph{") + str.Trim() + wxT("}");
            break;
          default:
            if (str.StartsWith(wxT("TeX:")))
            {
              str = GetEditable()->ToString();
              str = str.Mid(5, str.Length());
            }
            break;
        }
      }
      break;
  }

  return str;
}

wxString GroupCell::ToTeXCodeCell(wxString imgDir, wxString filename, int *imgCounter) const
{
  wxString str;
  Configuration *configuration = (*m_configuration);

  // Input cells
  if (configuration->ShowCodeCells())
  {
    // For LaTeX export we must use a dot as decimal separator
    // Save LC_NUMERIC, set it to "C", print out the float and then restore it.
    const std::string saved_lc_numeric{setlocale(LC_NUMERIC, NULL)}; // get current LC_NUMERIC locale
    setlocale(LC_NUMERIC, "C");
    str += wxString::Format(
      "\n\n\\noindent\n%%%%%%%%%%%%%%%%\n%%%% INPUT:\n\\begin{minipage}[t]{%fem}\\color{red}\\bfseries\n",
      (double)configuration->GetLabelWidth()/14
      ) + m_inputLabel->ToTeX() +
      wxString("\n\\end{minipage}");
    setlocale(LC_NUMERIC, saved_lc_numeric.c_str());

    if (m_inputLabel->GetNext())
    {

      str += wxT("\n\\begin{minipage}[t]{\\textwidth}\\color{blue}\n") +
             m_inputLabel->GetNext()->ToTeX() + "\n\\end{minipage}";
    }
  }

  if (m_output != NULL)
  {
    str += wxT("\n%%%% OUTPUT:\n");
    // Need to define labelcolor if this is Copy as LaTeX!
    if (imgCounter == NULL)
      str += wxT("\\definecolor{labelcolor}{RGB}{100,0,0}\n");

    bool mathMode = false;

    for (Cell &tmp : OnDrawList(m_output.get()))
    {

      if (tmp.GetType() == MC_TYPE_IMAGE ||
          tmp.GetType() == MC_TYPE_SLIDE)
      {
        str << ToTeXImage(&tmp, imgDir, filename, imgCounter);
      }
      else
      {
        switch (tmp.GetStyle())
        {

          case TS_LABEL:
          case TS_USERLABEL:
            if (mathMode)
              str += wxT("\\mbox{}\\]\n\\[\\displaystyle ");
            else
            {
              str += wxT("\\[\\displaystyle ");
              mathMode = true;
            }
            str += tmp.ToTeX() + wxT("\n");
            break;

          case TS_STRING:
            if (mathMode)
            {
              str += wxT("\\mbox{}\n\\]");
              mathMode = false;
            }
            str += TexEscapeOutputCell(tmp.ToTeX()) + wxT("\n");
            break;

          default:
            if (!mathMode)
            {
              str += wxT("\\[\\displaystyle ");
              mathMode = true;
            }
            str += tmp.ToTeX();
            break;
        }
      }
    }

    if (mathMode)
    {
      // Some invisible dummy content that keeps TeX happy if there really is
      // no output to display.
      str += wxT("\\mbox{}\n\\]\n%%%%%%%%%%%%%%%%");
    }
  }
  else
    str+=wxT("\n\n\\noindent%\n");

  return str;
}

wxString GroupCell::ToTeXImage(Cell *tmp, wxString imgDir, wxString filename, int *imgCounter)
{
  wxASSERT_MSG((imgCounter != NULL), _("Bug: No image counter to write to!"));
  if (imgCounter == NULL) return wxEmptyString;

  wxString str;

  if (imgDir != wxEmptyString)
  {
    auto const copy = tmp->Copy();
    auto *const imgCopy = dynamic_cast<ImgCell *>(copy.get());
    (*imgCounter)++;
    wxString image = filename + wxString::Format(wxT("_%d"), *imgCounter);
    if (!wxDirExists(imgDir))
      if (!wxMkdir(imgDir))
        return wxEmptyString;

    wxString file = imgDir + wxT("/") + image + wxT(".") + imgCopy->GetExtension();
    if (imgCopy->ToImageFile(file).x >= 0)
      str += wxT("\\includegraphics[width=.95\\linewidth,height=.80\\textheight,keepaspectratio]{") +
        filename + wxT("_img/") + image + wxT("}");
    else
      str << wxT("\n\\verb|<<GRAPHICS>>|\n");
  }

  return str;
}

wxString GroupCell::ToXML() const
{
  wxString str;
  str = wxT("\n<cell"); // start opening tag
  // write "type" according to m_groupType
  switch (m_groupType)
  {
    case GC_TYPE_CODE:
    {
      str += wxT(" type=\"code\"");
      int i = 0;
      for(StringHash::const_iterator it = m_knownAnswers.begin();
          it != m_knownAnswers.end();
          ++it)
      {
        i++;
        // In theory the attribute should be saved and read verbatim, with the exception
        // of the characters XML wants to be quoted. In reality wxWidget's newline handling
        // seems to be broken => escape newlines.
        wxString question = Cell::XMLescape(it->first);
        question.Replace(wxT("\n"),wxT("&#10;"));
        wxString answer = Cell::XMLescape(it->second);
        answer.Replace(wxT("\n"),wxT("&#10;"));
        str += wxString::Format(wxT(" question%i=\""),i) + question + wxT("\"");
        str += wxString::Format(wxT(" answer%i=\""),i) + answer + wxT("\"");
      }
      
      if(m_autoAnswer)
        str += wxT(" auto_answer=\"yes\"");
      break;
    }
    case GC_TYPE_IMAGE:
      str += wxT(" type=\"image\"");
      break;
    case GC_TYPE_TEXT:
      str += wxT(" type=\"text\"");
      break;
    case GC_TYPE_TITLE:
      str += wxT(" type=\"title\" sectioning_level=\"1\"");
      break;
    case GC_TYPE_SECTION:
      str += wxT(" type=\"section\" sectioning_level=\"2\"");
      break;
    case GC_TYPE_SUBSECTION:
      str += wxT(" type=\"subsection\" sectioning_level=\"3\"");
      break;
    case GC_TYPE_SUBSUBSECTION:
      // We save subsubsections as subsections with a higher sectioning level:
      // This makes them backwards-compatible in the way that they are displayed
      // as subsections on old wxMaxima installations.
      str += wxT(" type=\"subsection\" sectioning_level=\"4\"");
      break;
    case GC_TYPE_HEADING5:
      str += wxT(" type=\"subsection\" sectioning_level=\"5\"");
      break;
    case GC_TYPE_HEADING6:
      str += wxT(" type=\"subsection\" sectioning_level=\"6\"");
      break;
    case GC_TYPE_PAGEBREAK:
    {
      str += wxT(" type=\"pagebreak\"/>");
      return str;
    }
      break;
    default:
      str += wxT(" type=\"unknown\"");
      break;
  }

  if(GetSuppressTooltipMarker())
    str += wxT(" hideToolTip=\"true\"");

  // write hidden status
  if (IsHidden())
    str += wxT(" hide=\"true\"");
  str += wxT(">\n");

  Cell *input = GetEditable();
  Cell *output = GetLabel();
  // write contents
  switch (m_groupType)
  {
    case GC_TYPE_CODE:
      if (input != NULL)
      {
        str += wxT("<input>\n");
        str += input->ListToXML();
        str += wxT("</input>");
      }
      if (output != NULL)
      {
        str += wxT("\n<output>\n");
        str += wxT("<mth>");
        str += output->ListToXML();
        str += wxT("\n</mth></output>");
      }
      break;
    case GC_TYPE_IMAGE:
      if (input != NULL)
        str += input->ListToXML();
      if (output != NULL)
        str += output->ListToXML();
      break;
    case GC_TYPE_TEXT:
      if (input)
        str += input->ListToXML();
      break;
    case GC_TYPE_TITLE:
    case GC_TYPE_SECTION:
    case GC_TYPE_SUBSECTION:
    case GC_TYPE_SUBSUBSECTION:
    case GC_TYPE_HEADING5:
    case GC_TYPE_HEADING6:
      if (input)
        str += input->ListToXML();
      if (m_hiddenTree)
      {
        str += wxT("<fold>");
        str += m_hiddenTree->ListToXML();
        str += wxT("</fold>");
      }
      break;
    default:
    {
      for (const Cell &tmp : OnList(output))
        // cppcheck-suppress useStlAlgorithm
        str += tmp.ListToXML();

      break;
    }
  }
  str += wxT("\n</cell>\n");

  return str;
}

Cell::Range GroupCell::GetInnerCellsInRect(const wxRect &rect) const
{
  if (m_inputLabel->ContainsRect(rect))
    return m_inputLabel->GetCellsInRect(rect);
  if (m_output && !IsHidden() && m_outputRect.Contains(rect))
    return m_output->GetCellsInRect(rect);
  return {const_cast<GroupCell*>(this), const_cast<GroupCell*>(this)};
}

Cell::Range GroupCell::GetCellsInOutputRect(const wxRect &rect, const wxPoint one, const wxPoint two) const
{
  if (IsHidden())
    return {};

  wxPoint start = two, end = one;
  if (one.y < two.y || (one.y == two.y && one.x < two.x))
  {
    start = one;
    end = two;
  }

  // Lets select a rectangle
  Range r = m_output->GetListCellsInRect(rect);

  if (!r.first)
    return {};

  wxASSERT(r.last);

  // If selection is on multiple lines, we need to correct it
  if (r.first->GetCurrentY() != r.last->GetCurrentY())
  {
    Cell *const tmp = r.last;

    // Find the first cell in selection
    for (Cell &curr : OnDrawList(r.first)) {
      r.first = &curr;
      if (&curr == tmp ||
           (curr.GetCurrentX() + curr.GetWidth() >= start.x
            && curr.GetCurrentY() + curr.GetDrop() >= start.y))
        break;
    }

    // Find the last cell in selection
    r.last = r.first;
    for (Cell &curr : OnDrawList(r.first->GetNext()))
    {
      if (curr.GetCurrentX() <= end.x &&
          curr.GetCurrentY() - curr.GetCenterList() <= end.y)
        r.last = &curr;
      if (&curr == tmp)
        break;
    }
  }

  if (r.first == r.last)
    r = r.first->GetInnerCellsInRect(rect);
  return r;
}

const wxString &GroupCell::GetToolTip(const wxPoint point) const
{
  // TODO: There's a question of whether we want to return
  // the local tooltip, or empty string (latter would be in line
  // with Cell's behavior.
  if (!ContainsPoint(point))
    return GetLocalToolTip();

  // Default assumption: will be overwritten by the next command,
  // if there is a more accurate solution.
  m_cellPointers->m_cellUnderPointer = const_cast<GroupCell*>(this);

  const wxString *retval = &GetLocalToolTip();

  if (IsHidden())
    return *retval;
  
  for (auto &tmp : OnList(m_output.get()))
  {
    // If a cell contains a cell containing a tooltip, the tooltip of the
    // containing cell will be overridden.
    // TODO: Why do we keep iterating? Is there a reason to return the
    // tooltip of the last cell with a tooltip, instead of the first one?

    auto &toolTip = tmp.GetToolTip(point);
    if (!toolTip.empty())
      retval = &toolTip;
  }

  return *retval;
}

// cppcheck-suppress functionConst
bool GroupCell::SetEditableContent(const wxString &text)
{
  if (GetEditable())
  {
    GetEditable()->SetValue(text);
    return true;
  }
  else
    return false;
}

void GroupCell::BreakLines()
{
  Cell * cell = m_output.get();

  if(cell == NULL)
    return;

  // 1st step: Break 2d objects that are wider than a line into lines
  if(UnBreakUpCells(cell))
  {
    m_output->ResetCellListSizesList();
    m_output->RecalculateList((*m_configuration)->GetMathFontSize());
  }
  if(BreakUpCells(cell))
  {
    m_output->ResetCellListSizesList();
    m_output->RecalculateList((*m_configuration)->GetMathFontSize());
  }

  // 2nd step: Determine a sane maximum line width
  int fullWidth = (*m_configuration)->GetClientWidth();
  Configuration *configuration = (*m_configuration);
  int currentWidth = GetLineIndent(cell);
  if((cell->GetStyle() != TS_LABEL) && (cell->GetStyle() != TS_USERLABEL))
    fullWidth -= configuration->GetIndent();

  // Don't let the layout degenerate for small window widths
  if (fullWidth < Scale_Px(150)) fullWidth = Scale_Px(150);

  // 3rd step: break the output into lines.
  if (!IsHidden()) {
    bool prevBroken = false;
    for (Cell &tmp : OnDrawList(cell))
    {
      if (prevBroken) {
        currentWidth += GetLineIndent(&tmp);
        prevBroken = false;
      }
      int const cellWidth = tmp.GetWidth();
      tmp.SoftLineBreak(false);
      if (tmp.BreakLineHere() || (currentWidth + cellWidth >= fullWidth))
      {
        tmp.SoftLineBreak(true);
        currentWidth = 0;
        prevBroken = true;
      }
      currentWidth += cellWidth;
    }
  }
  m_output->ResetDataList();
  ResetCellListSizes();
}

Cell::Range GroupCell::GetCellsInOutput() const
{
  if (IsHidden())
    return {};

  Range r = {};

  for (Cell &tmp : OnDrawList(m_output.get()))
  {
    r.first = &tmp;
    if (tmp.GetStyle() == TS_LABEL || tmp.GetStyle() == TS_USERLABEL)
      break;
  }

  for (Cell &tmp : OnDrawList(r.first))
    r.last = &tmp;

  return r;
}

bool GroupCell::BreakUpCells(Cell *cell)
{
  if(cell == NULL)
    return false;

  int showLength;
  switch ((*m_configuration)->ShowLength())
  {
  case 0:
    showLength = 5000;
    break;
  case 1:
    showLength = 10000;
    break;
  case 2:
    showLength = 25000;
    break;
  case 3:
    showLength = 50000;
    break;
  default:
    showLength = 500;    
  }

  // Reduce the number of steps involved in layouting big equations
  if(m_cellsInGroup > showLength)
  {
    bool lineHeightsChanged = false;
    wxLogMessage(_("Resolving to 1D layout for one cell in order to save time"));
    if (!IsHidden())
      for (Cell &tmp : OnDrawList(cell))
        lineHeightsChanged |= tmp.BreakUp();
    return lineHeightsChanged;
  }

  return BreakUp();
}

bool GroupCell::UnBreakUpCells(Cell *cell)
{
  int showLength;
  switch ((*m_configuration)->ShowLength())
  {
  case 0:
    showLength = 50;
    break;
  case 1:
    showLength = 500;
    break;
  case 2:
    showLength = 2500;
    break;
  case 3:
    showLength = 5000;
    break;
  default:
    showLength = 500;    
  }

  // Reduce the number of steps involved in layouting big equations
  if(m_cellsInGroup > showLength)
  {
    wxLogMessage(_("Resolving to linear layout for one big cell in order to save time"));
    return true;
  }
 
  bool retval = false;
  for (Cell &tmp : OnDrawList(cell))
    if (tmp.IsBrokenIntoLines())
    {
      tmp.Unbreak();
      retval = true;
    }

  return retval;
}

// support for hiding text, code cells

void GroupCell::Hide(bool hide)
{
  if (IsFoldable())
    return;

  if (IsHidden() == hide)
    return;

  Cell::Hide(hide);
  if ((m_groupType == GC_TYPE_TEXT) || (m_groupType == GC_TYPE_CODE))
    GetEditable()->SetFirstLineOnly(hide);

  // Don't keep cached versions of scaled images around if they aren't visible at all.
  if (GetLabel())
    GetLabel()->ClearCacheList();

  ResetSize();
  GetEditable()->ResetSize();
}

void GroupCell::SwitchHide()
{
  Hide(!IsHidden());
}

//
// support for folding/unfolding sections
//
bool GroupCell::HideTree(std::unique_ptr<GroupCell> &&tree)
{
  if (m_hiddenTree)
    return false;
  m_hiddenTree = std::move(tree);
  m_hiddenTree->SetHiddenTreeParent(this);

  // Clear cached images from cells that are hidden
  for (auto &tmp : OnList(m_hiddenTree.get()))
  {
    if (tmp.GetLabel())
      tmp.GetLabel()->ClearCacheList();
  }

  return true;
}

std::unique_ptr<GroupCell> GroupCell::UnhideTree()
{
  m_hiddenTree->SetHiddenTreeParent(m_hiddenTreeParent);
  return std::move(m_hiddenTree);
}

/**
 * Unfold a tree from the bottom up, when a hidden cell needs to be seen.
 *
 * @return true if any cells were unfolded.
 */
bool GroupCell::RevealHidden()
{
  if (!m_hiddenTreeParent)
    return false;
  m_hiddenTreeParent->RevealHidden();
  m_hiddenTreeParent->Unfold();
  return true;
}

/**
 * For every cell in this GroupCell, set m_hiddenTreeParent to parent.
 * This way, the field can be used to traverse up the tree no matter which
 * child we are on. In other words, every child knows its parent node.
 */
void GroupCell::SetHiddenTreeParent(GroupCell *parent)
{
  for (auto &cell : OnList(this))
    cell.m_hiddenTreeParent = parent;
}

GroupCell *GroupCell::Fold()
{
  if (!IsFoldable() || m_hiddenTree) // already folded?? shouldn't happen
    return NULL;
  if (GetNext() == NULL)
    return NULL;
  int nextgct = GetNext()->GetGroupType(); // groupType of the next cell
  if ((m_groupType == nextgct) || IsLesserGCType(nextgct))
    return NULL; // if the next gc shouldn't be folded, exit

  // now there is at least one cell to fold (at least m_next)
  GroupCell *end = GetNext();

  while (end != NULL)
  {
    if (end->GetLabel())
      end->GetLabel()->ClearCacheList();

    GroupCell *tmp = end->GetNext();
    if (tmp == NULL)
      break;
    if ((m_groupType == tmp->GetGroupType()) || IsLesserGCType(tmp->GetGroupType()))
      break; // the next one of the end is not suitable for folding, break
    end = tmp;
  }
  wxASSERT(end);

  auto tornOut = CellList::TearOut(GetNext(), end);
  wxASSERT(tornOut.cellOwner);
  m_hiddenTree = static_unique_ptr_cast<GroupCell>(std::move(tornOut.cellOwner));
  m_hiddenTree->SetHiddenTreeParent(this);
  return this;
}

// unfolds the m_hiddenTree beneath this cell
// be careful to update m_last if this happens in the main tree in MathCtrl
GroupCell *GroupCell::Unfold()
{
  if (!IsFoldable() || !m_hiddenTree)
    return NULL;

  auto splicedIn = CellList::SpliceInAfter(this, std::move(m_hiddenTree));
  GetNext()->SetHiddenTreeParent(m_hiddenTreeParent);
  return dynamic_cast<GroupCell *>(splicedIn.lastSpliced);
}

GroupCell *GroupCell::FoldAll()
{
  GroupCell *result = NULL;
  for (auto &tmp : OnList(this))
  {
    if (tmp.IsFoldable() && !tmp.m_hiddenTree)
    {
      tmp.Fold();
      result = &tmp;
    }
    if (tmp.m_hiddenTree != NULL)
      tmp.m_hiddenTree->FoldAll();
  }
  return result;
}

// unfolds recursivly its contents
// if (all) then also calls it on it's m_next
GroupCell *GroupCell::UnfoldAll()
{
  GroupCell *result = NULL;
  for (auto &tmp : OnList(this))
  {
    if (tmp.IsFoldable() && (tmp.m_hiddenTree != NULL))
    {
      tmp.Unfold();
      result = &tmp;
    }
    if (tmp.m_hiddenTree != NULL)
      tmp.m_hiddenTree->UnfoldAll();
  }
  return result;
}

bool GroupCell::IsLesserGCType(int comparedTo) const
{
  switch (m_groupType)
  {
    case GC_TYPE_CODE:
    case GC_TYPE_TEXT:
    case GC_TYPE_PAGEBREAK:
    case GC_TYPE_IMAGE:
      return (comparedTo == GC_TYPE_TITLE) || (comparedTo == GC_TYPE_SECTION) ||
             (comparedTo == GC_TYPE_SUBSECTION) || (comparedTo == GC_TYPE_SUBSUBSECTION) ||
        (comparedTo == GC_TYPE_HEADING5) || (comparedTo == GC_TYPE_HEADING6);
    case GC_TYPE_HEADING6:
      return (comparedTo == GC_TYPE_TITLE) || (comparedTo == GC_TYPE_SECTION)
             || (comparedTo == GC_TYPE_SUBSECTION) || (comparedTo == GC_TYPE_SUBSUBSECTION) ||
        (comparedTo == GC_TYPE_HEADING5);
    case GC_TYPE_HEADING5:
      return (comparedTo == GC_TYPE_TITLE) || (comparedTo == GC_TYPE_SECTION)
             || (comparedTo == GC_TYPE_SUBSECTION)|| (comparedTo == GC_TYPE_SUBSUBSECTION);
    case GC_TYPE_SUBSUBSECTION:
      return (comparedTo == GC_TYPE_TITLE) || (comparedTo == GC_TYPE_SECTION)
             || (comparedTo == GC_TYPE_SUBSECTION);
    case GC_TYPE_SUBSECTION:
      return (comparedTo == GC_TYPE_TITLE) || (comparedTo == GC_TYPE_SECTION);
    case GC_TYPE_SECTION:
      return comparedTo == GC_TYPE_TITLE;
    case GC_TYPE_TITLE:
      return false;
    default:
      return false;
  }
}

void GroupCell::Number(int &section, int &subsection, int &subsubsection, int &heading5, int &heading6, int &image) const
{
  for (auto const &tmp : OnList(this))
  {
    switch (tmp.m_groupType)
    {
      case GC_TYPE_TITLE:
        section = subsection = subsubsection = heading5 = heading6 = 0;
        break;
      case GC_TYPE_SECTION:
        section++;
        subsection = subsubsection = heading5 = heading6 = 0;
        {
          wxString num = wxT("  ");
          num << section << wxT(" ");
          tmp.m_inputLabel->SetValue(num);
          tmp.m_inputLabel->SetStyle(TS_SECTION);
        }
        break;
      case GC_TYPE_SUBSECTION:
        subsubsection = heading5 = heading6 = 0;
        subsection++;
        {
          wxString num = wxT("  ");
          num << section << wxT(".") << subsection << wxT(" ");
          tmp.m_inputLabel->SetValue(num);
          tmp.m_inputLabel->SetStyle(TS_SUBSECTION);
        }
        break;
      case GC_TYPE_SUBSUBSECTION:
        heading5 = heading6 = 0;
        subsubsection++;
        {
          wxString num = wxT("  ");
          num << section << wxT(".") << subsection << wxT(".") << subsubsection << wxT(" ");
          tmp.m_inputLabel->SetValue(num);
          tmp.m_inputLabel->SetStyle(TS_SUBSUBSECTION);
        }
        break;
      case GC_TYPE_HEADING5:
        heading5++;
        heading6 = 0;
        {
          wxString num = wxT("  ");
          num << section << wxT(".") << subsection << wxT(".") << subsubsection << wxT(".")
              << heading5 << wxT(" ");
          tmp.m_inputLabel->SetValue(num);
          tmp.m_inputLabel->SetStyle(TS_HEADING5);
        }
        break;
      case GC_TYPE_HEADING6:
        heading6++;
        {
          wxString num = wxT("  ");
          num << section << wxT(".") << subsection << wxT(".") << subsubsection << wxT(".")
              << heading5 << wxT(".") << heading6 << wxT(" ");
          tmp.m_inputLabel->SetValue(num);
          tmp.m_inputLabel->SetStyle(TS_HEADING6);
        }
        break;
      case GC_TYPE_IMAGE:
        image++;
        {
          wxString num = wxString::Format(_("Figure %d:"), image);
          tmp.m_inputLabel->SetValue(num);
        }
        break;
      default:
        break;
    }

    if (IsFoldable() && tmp.m_hiddenTree)
      tmp.m_hiddenTree->Number(section, subsection, subsubsection, heading5, heading6, image);
  }
}

bool GroupCell::IsMainInput(Cell *active) const
{
  return active && active == m_inputLabel->GetNext();
}

bool GroupCell::Contains(GroupCell *cell) const
{
  for (auto const &tmp : OnList(this))
  {
    // If this is the cell we search for we can end the search.
    if (&tmp == cell)
      return true;

    // If this cell contains a hidden tree we have to search that at well.
    if ((tmp.IsFoldable()) && (tmp.GetHiddenTree()) != NULL)
    {
      if (tmp.GetHiddenTree()->Contains(cell))
        return true;
    }
  }

  return false;
}

#if wxUSE_ACCESSIBILITY
wxAccStatus GroupCell::GetDescription(int childId, wxString *description) const
{
  if (description == NULL)
    return wxACC_FAIL;
  
  if (childId == 0)
  {
    if (m_groupType == GC_TYPE_PAGEBREAK)
    {
      *description = _("A page break");
      return wxACC_OK;
    }
    else
    {
      *description = _("A GroupCell that bundles input with its output");
      return wxACC_OK;
    }
  }
  else
  {
    Cell *childCell = nullptr;
    if (GetChild(childId, &childCell) == wxACC_OK)
    {
      return childCell->GetDescription(0, description);
    }
  }
  
  *description = wxEmptyString;
  return wxACC_FAIL;
}


wxAccStatus GroupCell::GetLocation(wxRect &rect, int elementId)
{
  if(elementId == 0)
  {
    rect = wxRect(GetRect().GetTopLeft()     + (*m_configuration)->GetVisibleRegion().GetTopLeft(),
                  GetRect().GetBottomRight() + (*m_configuration)->GetVisibleRegion().GetTopLeft());
    
    // Our GroupCell handles the hcaret below the cell, as well as its contents
    rect.SetBottom(rect.GetBottom()+(*m_configuration)->GetGroupSkip());
    
    // If we are the 1st groupcell of the worksheet we handle the hcaret above this
    // cell, too.
    if (!GetPrevious())
      rect.SetTop(rect.GetTop()-(*m_configuration)->GetGroupSkip());
    
    if(rect.GetTop() < 0)
      rect.SetTop(0);
    if(rect.GetLeft() < 0)
      rect.SetLeft(0);
    if(rect.GetBottom() > (*m_configuration)->GetVisibleRegion().GetWidth())
      rect.SetBottom((*m_configuration)->GetVisibleRegion().GetWidth());
    if(rect.GetRight() > (*m_configuration)->GetVisibleRegion().GetHeight())
      rect.SetRight((*m_configuration)->GetVisibleRegion().GetHeight());
    rect = wxRect(rect.GetTopLeft()+(*m_configuration)->GetWorksheetPosition(),rect.GetBottomRight()+(*m_configuration)->GetWorksheetPosition());
    return wxACC_OK;
  }
  else
  {
    Cell *childCell = nullptr;
    if (GetChild(elementId, &childCell) == wxACC_OK)
      return childCell->GetLocation(rect, 0);
  }
  return wxACC_FAIL;
}

#endif

void CellList::Check(const GroupCell *c)
{
  if (!c) return;
  wxASSERT_MSG(!c->m_next || dynamic_cast<GroupCell*>(c->m_next.get()),
               _("Bug: The successor to a GroupCell is not a GroupCell."));
  wxASSERT_MSG(!c->m_previous || dynamic_cast<GroupCell*>(c->m_previous),
               _("Bug: The predecessor to a GroupCell is not a GroupCell."));
  CellList::Check(static_cast<const Cell *>(c));
}

wxString GroupCell:: m_lookalikeChars(
    wxT("µ")		wxT("\u03bc")
    wxT("\u2126")	wxT("\u03a9")
    wxT("C")		wxT("\u03F2")
    wxT("C")		wxT("\u0421")
    wxT("\u03F2")	wxT("\u0421")
    wxT("A")		wxT("\u0391")
    wxT("A")		wxT("\u0410")
    wxT("\u0391")	wxT("\u0410")
    wxT("M")		wxT("\u0392")
    wxT("E")		wxT("\u0395")
    wxT("E")		wxT("\u0415")
    wxT("\u0415")	wxT("\u0395")
    wxT("Z")		wxT("\u0396")
    wxT("H")		wxT("\u0397")
    wxT("H")		wxT("\u041D")
    wxT("\u0397")	wxT("\u041D")
    wxT("I")		wxT("\u0399")
    wxT("I")		wxT("\u0406")
    wxT("l")		wxT("\u0406")
    wxT("K")		wxT("\u039A")
    wxT("K")		wxT("\u041A")
    wxT("\u039A")	wxT("\u041A")
    wxT("\u212a")	wxT("\u041A")
    wxT("K")		wxT("\u212A")
    wxT("M")		wxT("\u041c")
    wxT("\u039C")	wxT("\u041c")
    wxT("M")		wxT("\u039C")
    wxT("N")		wxT("\u039D")
    wxT("O")		wxT("\u039F")
    wxT("O")		wxT("\u041E")
    wxT("\u039F")	wxT("\u041E")
    wxT("\u039F")	wxT("\u041E")
    wxT("P")		wxT("\u03A1")
    wxT("X")		wxT("\u0425")
    wxT("e")		wxT("\u0435")
    wxT("p")		wxT("\u0440")
    wxT("x")		wxT("\u0445")
    wxT("y")		wxT("\u0443")
    wxT("P")		wxT("\u0420")
    wxT("\u03A1")	wxT("\u0420")
    wxT("T")		wxT("\u03A4")
    wxT("T")		wxT("\u0422")
    wxT("\u03A4")	wxT("\u0422")
    wxT("Y")		wxT("\u03A5")
    wxT("\u212a")	wxT("\u039A")
    wxT("l")		wxT("I")
    wxT("B")		wxT("\u0392")
    wxT("S")		wxT("\u0405")
    wxT("\u0392")	wxT("\u0412")
    wxT("B")		wxT("\u0412")
    wxT("J")		wxT("\u0408")
    wxT("a")		wxT("\u0430")
    wxT("o")		wxT("\u03bf")
    wxT("\u03a3")      wxT("\u2211")
    wxT("o")		wxT("\u043e")
    wxT("\u03bf")	wxT("\u043e")
    wxT("c")		wxT("\u0441")
    wxT("s")		wxT("\u0455")
    wxT("t")		wxT("\u03c4")
    wxT("u")		wxT("\u03c5")
    wxT("x")		wxT("\u03c7")
    wxT("ü")		wxT("\u03cb")
    wxT("\u0460")	wxT("\u03c9")
    wxT("\u0472")	wxT("\u0398")
    wxT("У")	        wxT("Y")
    wxT("У")	        wxT("y")
    wxT("ѡ")	        wxT("ω")
    wxT("Ѳ")	        wxT("Θ")
    wxT("θ")	        wxT("ѳ")
    wxT("ø")	        wxT("⌀")
    wxT("∅")	        wxT("⊘")
    wxT("∅")	        wxT("⌀")
    wxT("ø")	        wxT("⊘")
    wxT("ø")            wxT("∅")
    wxT("⌀")            wxT("⊘")
  );
