You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
387 lines
15 KiB
C++
387 lines
15 KiB
C++
/*
|
|
* Copyright (c) Contributors to the Open 3D Engine Project.
|
|
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
*
|
|
*/
|
|
|
|
#include "LUAEditorFoldingWidget.hxx"
|
|
|
|
#include "LUAEditorBlockState.h"
|
|
#include "LUAEditorPlainTextEdit.hxx"
|
|
#include "LUAEditorStyleMessages.h"
|
|
|
|
#include <Source/LUA/moc_LUAEditorFoldingWidget.cpp>
|
|
|
|
#include <QTextBlock>
|
|
#include <QStyleOption>
|
|
#include <QStyle>
|
|
#include <QPainter>
|
|
|
|
namespace LUAEditor
|
|
{
|
|
FoldingWidget::FoldingWidget(QWidget* pParent)
|
|
: QWidget(pParent)
|
|
{
|
|
setEnabled(false);
|
|
}
|
|
|
|
FoldingWidget::~FoldingWidget()
|
|
{
|
|
}
|
|
|
|
void FoldingWidget::paintEvent([[maybe_unused]] QPaintEvent* paintEvent)
|
|
{
|
|
auto colors = AZ::UserSettings::CreateFind<SyntaxStyleSettings>(AZ_CRC("LUA Editor Text Settings", 0xb6e15565), AZ::UserSettings::CT_GLOBAL);
|
|
|
|
auto cursor = m_textEdit->textCursor();
|
|
auto selectedBlock = m_textEdit->document()->findBlock(cursor.position());
|
|
int startSelectedFold = 0;
|
|
int endSelectedFold = 0;
|
|
|
|
if (selectedBlock.isValid()) //detect if this block closes a fold
|
|
{
|
|
QTBlockState blockState;
|
|
blockState.m_qtBlockState = selectedBlock.userState();
|
|
auto prevBlock = selectedBlock.previous();
|
|
if (!blockState.m_blockState.m_uninitialized && prevBlock.isValid())
|
|
{
|
|
QTBlockState prevBlockState;
|
|
prevBlockState.m_qtBlockState = prevBlock.userState();
|
|
if (blockState.m_blockState.m_foldLevel < prevBlockState.m_blockState.m_foldLevel)
|
|
{
|
|
selectedBlock = prevBlock;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (selectedBlock.isValid())
|
|
{
|
|
startSelectedFold = selectedBlock.blockNumber();
|
|
endSelectedFold = selectedBlock.blockNumber() + 1;
|
|
|
|
QTBlockState blockState;
|
|
blockState.m_qtBlockState = selectedBlock.userState();
|
|
if (!blockState.m_blockState.m_uninitialized && blockState.m_blockState.m_foldLevel > 0)
|
|
{
|
|
auto startBlock = selectedBlock;
|
|
while (startBlock.isValid())
|
|
{
|
|
auto prevBlock = startBlock.previous();
|
|
QTBlockState prevBlockState;
|
|
prevBlockState.m_qtBlockState = prevBlock.userState();
|
|
|
|
if (prevBlockState.m_blockState.m_foldLevel >= blockState.m_blockState.m_foldLevel)
|
|
{
|
|
startBlock = prevBlock;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
startSelectedFold = startBlock.blockNumber();
|
|
|
|
auto endBlock = selectedBlock;
|
|
while (endBlock.isValid())
|
|
{
|
|
auto nextBlock = endBlock.next();
|
|
QTBlockState nextBlockState;
|
|
nextBlockState.m_qtBlockState = nextBlock.userState();
|
|
|
|
if (nextBlockState.m_blockState.m_foldLevel >= blockState.m_blockState.m_foldLevel)
|
|
{
|
|
endBlock = nextBlock;
|
|
}
|
|
else //less than, which is included in this fold
|
|
{
|
|
endBlock = nextBlock;
|
|
break;
|
|
}
|
|
}
|
|
endSelectedFold = endBlock.blockNumber() + 1;
|
|
}
|
|
}
|
|
|
|
QStyleOption opt;
|
|
opt.init(this);
|
|
QPainter p(this);
|
|
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
|
|
AZ::u32 lastFoldLevel = 0;
|
|
bool firstBlock = true;
|
|
m_textEdit->ForEachVisibleBlock([&](const QTextBlock& block, const QRectF& blockRect)
|
|
{
|
|
QTBlockState blockState;
|
|
blockState.m_qtBlockState = block.userState();
|
|
if (!blockState.m_blockState.m_uninitialized)
|
|
{
|
|
if (firstBlock)
|
|
{
|
|
lastFoldLevel = 0;
|
|
auto prevBlock = block.previous();
|
|
if (prevBlock.isValid())
|
|
{
|
|
QTBlockState prevBlockState;
|
|
prevBlockState.m_qtBlockState = prevBlock.userState();
|
|
lastFoldLevel = prevBlockState.m_blockState.m_foldLevel;
|
|
}
|
|
firstBlock = false;
|
|
}
|
|
auto centerToBorder = (m_singleSize - 2 * c_borderSize) / 2;
|
|
QRect drawRect = blockRect.toRect();
|
|
drawRect.setLeft(0);
|
|
drawRect.setRight(m_singleSize);
|
|
|
|
auto oldPen = p.pen();
|
|
auto oldBrush = p.brush();
|
|
|
|
if (block.blockNumber() >= startSelectedFold && block.blockNumber() < endSelectedFold)
|
|
{
|
|
p.setPen(colors->GetFoldingSelectedColor());
|
|
}
|
|
else
|
|
{
|
|
p.setPen(colors->GetFoldingColor());
|
|
}
|
|
p.setBrush(QBrush(Qt::BrushStyle::NoBrush));
|
|
if (blockState.m_blockState.m_foldLevel > lastFoldLevel)
|
|
{
|
|
auto centerEdge = AZStd::max(m_singleSize / 10, 2); //for center lines, 10% of our size
|
|
auto center = drawRect.center();
|
|
QRect square(center.x() - centerToBorder, center.y() - centerToBorder, 2 * centerToBorder, 2 * centerToBorder);
|
|
p.drawRect(square);
|
|
p.drawLine(square.left() + centerEdge, center.y(), square.right() - centerEdge + 1, center.y());
|
|
if (blockState.m_blockState.m_folded)
|
|
{
|
|
p.drawLine(center.x(), square.top() + centerEdge, center.x(), square.bottom() - centerEdge + 1);
|
|
}
|
|
p.drawLine(center.x(), square.bottom() + 1, center.x(), drawRect.bottom());
|
|
if (blockState.m_blockState.m_foldLevel > 1)
|
|
{
|
|
if (block.blockNumber() == startSelectedFold)
|
|
{
|
|
p.setPen(colors->GetFoldingColor());
|
|
}
|
|
p.drawLine(center.x(), drawRect.top(), center.x(), square.top() - 1);
|
|
}
|
|
}
|
|
else if (blockState.m_blockState.m_foldLevel < lastFoldLevel)
|
|
{
|
|
auto center = drawRect.center();
|
|
p.drawLine(center.x(), drawRect.top(), center.x(), center.y());
|
|
p.drawLine(center.x(), center.y(), drawRect.right(), center.y());
|
|
if (blockState.m_blockState.m_foldLevel > 0)
|
|
{
|
|
if (block.blockNumber() == endSelectedFold - 1)
|
|
{
|
|
p.setPen(colors->GetFoldingColor());
|
|
}
|
|
p.drawLine(center.x(), center.y(), center.x(), drawRect.bottom());
|
|
}
|
|
}
|
|
else if (blockState.m_blockState.m_foldLevel > 0)
|
|
{
|
|
auto center = drawRect.center();
|
|
p.drawLine(center.x(), drawRect.top(), center.x(), drawRect.bottom());
|
|
}
|
|
|
|
lastFoldLevel = blockState.m_blockState.m_foldLevel;
|
|
p.setPen(oldPen);
|
|
p.setBrush(oldBrush);
|
|
}
|
|
});
|
|
}
|
|
|
|
void FoldingWidget::mouseReleaseEvent(QMouseEvent* event)
|
|
{
|
|
auto mousePos = event->localPos();
|
|
|
|
m_textEdit->ForEachVisibleBlock([&](QTextBlock& blockClicked, const QRectF& blockRect)
|
|
{
|
|
if (mousePos.y() >= blockRect.top() && mousePos.y() <= blockRect.bottom())
|
|
{
|
|
auto prevBlock = blockClicked.previous();
|
|
QTBlockState state;
|
|
state.m_qtBlockState = blockClicked.userState();
|
|
|
|
AZ::u32 prevFoldLevel = 0;
|
|
if (prevBlock.isValid())
|
|
{
|
|
QTBlockState prevBlockState;
|
|
prevBlockState.m_qtBlockState = prevBlock.userState();
|
|
prevFoldLevel = prevBlockState.m_blockState.m_foldLevel;
|
|
}
|
|
if (!state.m_blockState.m_uninitialized && state.m_blockState.m_foldLevel > prevFoldLevel)
|
|
{
|
|
state.m_blockState.m_folded = 1 - state.m_blockState.m_folded;
|
|
blockClicked.setUserState(state.m_qtBlockState);
|
|
|
|
int startDirty = blockClicked.position();
|
|
int dirtyLength = (blockClicked.position() + blockClicked.length()) - startDirty;
|
|
|
|
auto nextBlock = blockClicked.next();
|
|
QTBlockState nextState;
|
|
nextState.m_qtBlockState = nextBlock.userState();
|
|
bool currentChildFolded = false;
|
|
AZ::u32 currentChildFoldLevel = state.m_blockState.m_foldLevel;
|
|
while (nextBlock.isValid() && nextState.m_blockState.m_foldLevel >= state.m_blockState.m_foldLevel)
|
|
{
|
|
if (state.m_blockState.m_folded)
|
|
{
|
|
nextBlock.setVisible(false);
|
|
}
|
|
else // we are unfolding, need to preserve any child folds that were already folded before we were
|
|
{
|
|
if (!currentChildFolded)
|
|
{
|
|
nextBlock.setVisible(true);
|
|
if (nextState.m_blockState.m_foldLevel > currentChildFoldLevel)
|
|
{
|
|
currentChildFoldLevel = nextState.m_blockState.m_foldLevel;
|
|
if (nextState.m_blockState.m_folded)
|
|
{
|
|
currentChildFolded = true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bool visible = false;
|
|
if (nextState.m_blockState.m_foldLevel < currentChildFoldLevel)
|
|
{
|
|
currentChildFolded = false;
|
|
visible = true;
|
|
}
|
|
nextBlock.setVisible(visible);
|
|
}
|
|
}
|
|
dirtyLength = (nextBlock.position() + nextBlock.length()) - startDirty;
|
|
|
|
|
|
nextBlock = nextBlock.next();
|
|
nextState.m_qtBlockState = nextBlock.userState();
|
|
}
|
|
|
|
update();
|
|
m_textEdit->document()->markContentsDirty(startDirty, dirtyLength);
|
|
TextBlockFoldingChanged();
|
|
}
|
|
}
|
|
});
|
|
|
|
event->accept();
|
|
}
|
|
|
|
void FoldingWidget::OnContentChanged(int from, int, int charsAdded)
|
|
{
|
|
auto startBlock = m_textEdit->document()->findBlock(from);
|
|
if (!startBlock.isValid())
|
|
{
|
|
return;
|
|
}
|
|
|
|
auto endBlock = m_textEdit->document()->findBlock(from + charsAdded);
|
|
if (!endBlock.isValid())
|
|
{
|
|
endBlock = startBlock;
|
|
}
|
|
|
|
auto prevBlock = startBlock.previous();
|
|
bool atLeastOne = false;
|
|
while (prevBlock.isValid() && !prevBlock.isVisible())
|
|
{
|
|
startBlock = prevBlock;
|
|
prevBlock = prevBlock.previous();
|
|
atLeastOne = true;
|
|
}
|
|
if (atLeastOne) //need to grab the opening fold
|
|
{
|
|
startBlock = prevBlock;
|
|
}
|
|
|
|
auto nextBlock = endBlock.next();
|
|
while (nextBlock.isValid() && !nextBlock.isVisible())
|
|
{
|
|
endBlock = nextBlock;
|
|
nextBlock = nextBlock.next();
|
|
}
|
|
|
|
int startDirty = startBlock.position();
|
|
int dirtyLength = (endBlock.position() + endBlock.length()) - startDirty;
|
|
while (startBlock.isValid() && startBlock.blockNumber() <= endBlock.blockNumber())
|
|
{
|
|
QTBlockState state;
|
|
state.m_qtBlockState = startBlock.userState();
|
|
if (state.m_blockState.m_folded)
|
|
{
|
|
state.m_blockState.m_folded = 0;
|
|
startBlock.setUserState(state.m_qtBlockState);
|
|
}
|
|
startBlock.setVisible(true);
|
|
|
|
startBlock = startBlock.next();
|
|
}
|
|
|
|
update();
|
|
m_textEdit->document()->markContentsDirty(startDirty, dirtyLength);
|
|
TextBlockFoldingChanged();
|
|
}
|
|
|
|
void FoldingWidget::FoldAll()
|
|
{
|
|
AZ::u32 lastFoldLevel = 0;
|
|
for (auto block = m_textEdit->document()->begin(); block != m_textEdit->document()->end(); block = block.next())
|
|
{
|
|
block.setVisible(true);
|
|
|
|
QTBlockState state;
|
|
state.m_qtBlockState = block.userState();
|
|
|
|
if (state.m_blockState.m_foldLevel > lastFoldLevel)
|
|
{
|
|
state.m_blockState.m_folded = 1;
|
|
block.setUserState(state.m_qtBlockState);
|
|
|
|
if (lastFoldLevel != 0)
|
|
{
|
|
block.setVisible(false);
|
|
}
|
|
}
|
|
else if (state.m_blockState.m_foldLevel > 0)
|
|
{
|
|
block.setVisible(false);
|
|
}
|
|
|
|
lastFoldLevel = state.m_blockState.m_foldLevel;
|
|
}
|
|
|
|
update();
|
|
m_textEdit->document()->markContentsDirty(0, m_textEdit->document()->characterCount());
|
|
TextBlockFoldingChanged();
|
|
}
|
|
|
|
void FoldingWidget::UnfoldAll()
|
|
{
|
|
for (auto block = m_textEdit->document()->begin(); block != m_textEdit->document()->end(); block = block.next())
|
|
{
|
|
block.setVisible(true);
|
|
QTBlockState state;
|
|
state.m_qtBlockState = block.userState();
|
|
state.m_blockState.m_folded = 0;
|
|
block.setUserState(state.m_qtBlockState);
|
|
}
|
|
|
|
update();
|
|
m_textEdit->document()->markContentsDirty(0, m_textEdit->document()->characterCount());
|
|
TextBlockFoldingChanged();
|
|
}
|
|
|
|
void FoldingWidget::SetFont(QFont font)
|
|
{
|
|
QFontMetrics metrics(font);
|
|
m_singleSize = metrics.height();
|
|
setFixedWidth(m_singleSize);
|
|
}
|
|
}
|