Skip to content

Commit

Permalink
Initial work for showing BiDi control characters
Browse files Browse the repository at this point in the history
  • Loading branch information
IgKh committed Oct 15, 2024
1 parent cb35c16 commit 6efa97d
Show file tree
Hide file tree
Showing 12 changed files with 400 additions and 250 deletions.
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ set(SOURCES
katvan_previewerview.cpp
katvan_recentfiles.cpp
katvan_searchbar.cpp
katvan_settingsdialog.cpp
katvan_spellchecker.cpp
katvan_typstdriverwrapper.cpp
katvan_utils.cpp
Expand Down
44 changes: 23 additions & 21 deletions src/katvan_editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,6 @@

namespace katvan {

static constexpr QChar ALM_MARK = (ushort)0x061c;
static constexpr QChar LRM_MARK = (ushort)0x200e;
static constexpr QChar RLM_MARK = (ushort)0x200f;
static constexpr QChar LRI_MARK = (ushort)0x2066;
static constexpr QChar RLI_MARK = (ushort)0x2067;
static constexpr QChar PDI_MARK = (ushort)0x2069;

static constexpr QKeyCombination TEXT_DIRECTION_TOGGLE(Qt::CTRL | Qt::SHIFT | Qt::Key_X);
static constexpr QKeyCombination INSERT_POPUP(Qt::CTRL | Qt::SHIFT | Qt::Key_I);

Expand Down Expand Up @@ -153,6 +146,15 @@ void Editor::applyEffectiveSettings()

QTextOption textOption = document()->defaultTextOption();
textOption.setTabStopDistance(d_effectiveSettings.tabWidth() * fontMetrics().horizontalAdvance(QLatin1Char(' ')));

QTextOption::Flags flags;
#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)
if (d_effectiveSettings.showControlChars()) {
flags |= QTextOption::ShowDefaultIgnorables;
}
#endif

textOption.setFlags(flags);
document()->setDefaultTextOption(textOption);

EditorSettings::LineNumberStyle lineNumberStyle = d_effectiveSettings.lineNumberStyle();
Expand Down Expand Up @@ -184,19 +186,19 @@ QMenu* Editor::createInsertMenu()
{
QMenu* menu = new QMenu();

menu->addAction(tr("Right-to-Left Mark"), this, [this]() { insertMark(RLM_MARK); });
menu->addAction(tr("Left-to-Right Mark"), this, [this]() { insertMark(LRM_MARK); });
menu->addAction(tr("Arabic Letter Mark"), this, [this]() { insertMark(ALM_MARK); });
menu->addAction(tr("Right-to-Left Mark"), this, [this]() { insertMark(utils::RLM_MARK); });
menu->addAction(tr("Left-to-Right Mark"), this, [this]() { insertMark(utils::LRM_MARK); });
menu->addAction(tr("Arabic Letter Mark"), this, [this]() { insertMark(utils::ALM_MARK); });

menu->addSeparator();

menu->addAction(tr("Right-to-Left Isolate"), this, [this]() { insertSurroundingMarks(RLI_MARK, PDI_MARK); });
menu->addAction(tr("Left-to-Right Isolate"), this, [this]() { insertSurroundingMarks(LRI_MARK, PDI_MARK); });
menu->addAction(tr("Right-to-Left Isolate"), this, [this]() { insertSurroundingMarks(utils::RLI_MARK, utils::PDI_MARK); });
menu->addAction(tr("Left-to-Right Isolate"), this, [this]() { insertSurroundingMarks(utils::LRI_MARK, utils::PDI_MARK); });

menu->addSeparator();

QAction* insertInlineMathAction = menu->addAction(tr("Inline &Math"), this, [this]() {
insertSurroundingMarks(LRI_MARK + QStringLiteral("$"), QStringLiteral("$") + PDI_MARK);
insertSurroundingMarks(utils::LRI_MARK + QStringLiteral("$"), QStringLiteral("$") + utils::PDI_MARK);
});
insertInlineMathAction->setShortcut(Qt::CTRL | Qt::Key_M);

Expand Down Expand Up @@ -224,23 +226,23 @@ void Editor::setTextBlockDirection(Qt::LayoutDirection dir)
QString blockText = cursor.block().text();

if (dir == Qt::RightToLeft) {
if (blockText.startsWith(LRM_MARK)) {
if (blockText.startsWith(utils::LRM_MARK)) {
cursor.deleteChar();
blockText = blockText.sliced(1);
}
if (!blockText.startsWith(RLM_MARK)
&& !blockText.startsWith(ALM_MARK)
if (!blockText.startsWith(utils::RLM_MARK)
&& !blockText.startsWith(utils::ALM_MARK)
&& utils::naturalTextDirection(blockText) != dir) {
cursor.insertText(RLM_MARK);
cursor.insertText(utils::RLM_MARK);
}
}
else if (dir == Qt::LeftToRight) {
if (blockText.startsWith(RLM_MARK) || blockText.startsWith(ALM_MARK)) {
if (blockText.startsWith(utils::RLM_MARK) || blockText.startsWith(utils::ALM_MARK)) {
cursor.deleteChar();
blockText = blockText.sliced(1);
}
if (!blockText.startsWith(LRM_MARK) && utils::naturalTextDirection(blockText) != dir) {
cursor.insertText(LRM_MARK);
if (!blockText.startsWith(utils::LRM_MARK) && utils::naturalTextDirection(blockText) != dir) {
cursor.insertText(utils::LRM_MARK);
}
}

Expand Down Expand Up @@ -375,7 +377,7 @@ QString Editor::getIndentString(QTextCursor cursor) const

static bool isSingleBidiMark(QChar ch)
{
return ch == LRM_MARK || ch == RLM_MARK || ch == ALM_MARK;
return ch == utils::LRM_MARK || ch == utils::RLM_MARK || ch == utils::ALM_MARK;
}

static bool isSpace(QChar ch)
Expand Down
210 changes: 18 additions & 192 deletions src/katvan_editorsettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,6 @@
#include "katvan_editorsettings.h"

#include <QApplication>
#include <QButtonGroup>
#include <QDialogButtonBox>
#include <QFontComboBox>
#include <QFontDatabase>
#include <QFormLayout>
#include <QGridLayout>
#include <QGroupBox>
#include <QLabel>
#include <QSpinBox>
#include <QRadioButton>
#include <QVBoxLayout>

namespace katvan {

Expand Down Expand Up @@ -124,6 +113,9 @@ void EditorSettings::parseModeLine(QString mode)
d_lineNumberStyle = EditorSettings::LineNumberStyle::NONE;
}
}
else if (variable == QStringLiteral("show-control-chars")) {
d_showControlChars = parseModeLineBool(rest);
}
}
}

Expand Down Expand Up @@ -177,6 +169,14 @@ QString EditorSettings::toModeLine() const
break;
}
}
if (d_showControlChars) {
if (d_showControlChars.value()) {
result += QStringLiteral("show-control-chars on; ");
}
else {
result += QStringLiteral("show-control-chars off; ");
}
}

return result.trimmed();
}
Expand Down Expand Up @@ -227,6 +227,11 @@ EditorSettings::LineNumberStyle EditorSettings::lineNumberStyle() const
return d_lineNumberStyle.value_or(EditorSettings::LineNumberStyle::BOTH_SIDES);
}

bool EditorSettings::showControlChars() const
{
return d_showControlChars.value_or(true);
}

void EditorSettings::mergeSettings(const EditorSettings& other)
{
if (other.d_fontFamily) {
Expand All @@ -250,187 +255,8 @@ void EditorSettings::mergeSettings(const EditorSettings& other)
if (other.d_lineNumberStyle) {
d_lineNumberStyle = other.d_lineNumberStyle;
}
}

EditorSettingsDialog::EditorSettingsDialog(QWidget* parent)
: QDialog(parent)
{
setupUI();

updateFontSizes();
}

EditorSettings EditorSettingsDialog::settings() const
{
EditorSettings settings;

settings.setFontFamily(d_editorFontComboBox->currentFont().family());
settings.setFontSize(d_editorFontSizeComboBox->currentText().toInt());
settings.setLineNumberStyle(d_lineNumberStyle->currentData().value<EditorSettings::LineNumberStyle>());
settings.setIndentMode(d_indentMode->currentData().value<EditorSettings::IndentMode>());

if (d_indentWithSpaces->isChecked()) {
settings.setIndentStyle(EditorSettings::IndentStyle::SPACES);
}
else {
settings.setIndentStyle(EditorSettings::IndentStyle::TABS);
}

settings.setIndentWidth(d_indentWidth->value());
settings.setTabWidth(d_tabWidth->value());

return settings;
}

void EditorSettingsDialog::setSettings(const EditorSettings& settings)
{
QFont font = settings.font();
d_editorFontComboBox->setCurrentFont(font);
d_editorFontSizeComboBox->setCurrentText(QString::number(font.pointSize()));

QVariant lineNumberStyle = QVariant::fromValue(settings.lineNumberStyle());
d_lineNumberStyle->setCurrentIndex(d_lineNumberStyle->findData(lineNumberStyle));

QVariant indentMode = QVariant::fromValue(settings.indentMode());
d_indentMode->setCurrentIndex(d_indentMode->findData(indentMode));

if (settings.indentStyle() == EditorSettings::IndentStyle::SPACES) {
d_indentWithSpaces->setChecked(true);
}
else {
d_indentWithTabs->setChecked(true);
}

d_indentWidth->setValue(settings.indentWidth());
d_tabWidth->setValue(settings.tabWidth());

updateControlStates();
}

void EditorSettingsDialog::setupUI()
{
setWindowTitle(tr("Editor Settings"));

d_editorFontComboBox = new QFontComboBox();
connect(d_editorFontComboBox, &QFontComboBox::currentFontChanged, this, &EditorSettingsDialog::updateFontSizes);

QLabel* editorFontLabel = new QLabel(tr("Editor &Font:"));
editorFontLabel->setBuddy(d_editorFontComboBox);

d_editorFontSizeComboBox = new QComboBox();

d_lineNumberStyle = new QComboBox();
d_lineNumberStyle->addItem(tr("On Both Sides"), QVariant::fromValue(EditorSettings::LineNumberStyle::BOTH_SIDES));
d_lineNumberStyle->addItem(tr("On Primary Side"), QVariant::fromValue(EditorSettings::LineNumberStyle::PRIMARY_ONLY));
d_lineNumberStyle->addItem(tr("Don't Show"), QVariant::fromValue(EditorSettings::LineNumberStyle::NONE));

d_indentMode = new QComboBox();
d_indentMode->addItem(tr("None"), QVariant::fromValue(EditorSettings::IndentMode::NONE));
d_indentMode->addItem(tr("Normal"), QVariant::fromValue(EditorSettings::IndentMode::NORMAL));
d_indentMode->addItem(tr("Smart"), QVariant::fromValue(EditorSettings::IndentMode::SMART));

d_indentWithSpaces = new QRadioButton(tr("&Spaces"));
d_indentWithTabs = new QRadioButton(tr("&Tabs"));

QButtonGroup* indentStyleBtnGroup = new QButtonGroup(this);
indentStyleBtnGroup->addButton(d_indentWithSpaces);
indentStyleBtnGroup->addButton(d_indentWithTabs);
connect(indentStyleBtnGroup, &QButtonGroup::buttonToggled, this, &EditorSettingsDialog::updateControlStates);

d_indentWidth = new QSpinBox();
d_indentWidth->setSuffix(tr(" characters"));

d_tabWidth = new QSpinBox();
d_tabWidth->setSuffix(tr(" characters"));

QLabel* indentWidthLabel = new QLabel(tr("&Indent Width:"));
indentWidthLabel->setBuddy(d_indentWidth);

QLabel* tabWidthLabel = new QLabel(tr("Tab &Display Width:"));
tabWidthLabel->setBuddy(d_tabWidth);

QHBoxLayout* editorFontLayout = new QHBoxLayout();
editorFontLayout->addWidget(d_editorFontComboBox, 1);
editorFontLayout->addWidget(d_editorFontSizeComboBox);

QGroupBox* appearanceGroup = new QGroupBox(tr("Appearance"));
QFormLayout* appearanceLayout = new QFormLayout(appearanceGroup);
appearanceLayout->addRow(editorFontLabel, editorFontLayout);
appearanceLayout->addRow(tr("Show &Line Numbers:"), d_lineNumberStyle);

QGroupBox* indentationGroup = new QGroupBox(tr("Indentation"));
QVBoxLayout* indentationLayout = new QVBoxLayout(indentationGroup);

QFormLayout* indentationTopLayout = new QFormLayout();
indentationTopLayout->addRow(tr("Automatic Indentation:"), d_indentMode);

QGridLayout* indentationStyleLayout = new QGridLayout();
indentationStyleLayout->setColumnStretch(0, 3);
indentationStyleLayout->setColumnStretch(3, 1);

indentationStyleLayout->addWidget(new QLabel(tr("Indent with:")), 0, 0);
indentationStyleLayout->addWidget(d_indentWithSpaces, 1, 0);
indentationStyleLayout->addWidget(d_indentWithTabs, 2, 0);

indentationStyleLayout->addWidget(indentWidthLabel, 1, 2);
indentationStyleLayout->addWidget(d_indentWidth, 1, 3);
indentationStyleLayout->addWidget(tabWidthLabel, 2, 2);
indentationStyleLayout->addWidget(d_tabWidth, 2, 3);

indentationLayout->addLayout(indentationTopLayout);
indentationLayout->addLayout(indentationStyleLayout);

QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);

QVBoxLayout* mainLayout = new QVBoxLayout(this);
mainLayout->addWidget(appearanceGroup);
mainLayout->addWidget(indentationGroup);
mainLayout->addWidget(buttonBox);
}

void EditorSettingsDialog::updateControlStates()
{
d_indentWidth->setEnabled(!d_indentWithTabs->isChecked());
}

void EditorSettingsDialog::updateFontSizes()
{
QString currentFamily = d_editorFontComboBox->currentFont().family();

QList<int> pointSizes = QFontDatabase::pointSizes(currentFamily);
if (pointSizes.isEmpty()) {
// Might be a style tucked into the family name
qsizetype pos = currentFamily.lastIndexOf(QLatin1Char(' '));
if (pos > 0) {
pointSizes = QFontDatabase::pointSizes(currentFamily.sliced(0, pos).trimmed());
}
}

if (pointSizes.isEmpty()) {
// If still empty, just use defaults
pointSizes = QFontDatabase::standardSizes();
}

QString currentSizeText = d_editorFontSizeComboBox->currentText();
int currentSizeNewIndex = -1;

QStringList values;
values.reserve(pointSizes.size());
for (int size : std::as_const(pointSizes)) {
values.append(QString::number(size));

if (values.last() == currentSizeText) {
currentSizeNewIndex = values.size() - 1;
}
}

d_editorFontSizeComboBox->clear();
d_editorFontSizeComboBox->addItems(values);

if (currentSizeNewIndex >= 0) {
d_editorFontSizeComboBox->setCurrentIndex(currentSizeNewIndex);
if (other.d_showControlChars) {
d_showControlChars = other.d_showControlChars;
}
}

Expand Down
Loading

0 comments on commit 6efa97d

Please sign in to comment.