inial commit

This commit is contained in:
2023-10-29 21:41:19 +01:00
commit b600149a59
4 changed files with 495 additions and 0 deletions

37
CMakeLists.txt Normal file
View File

@ -0,0 +1,37 @@
cmake_minimum_required(VERSION 3.16 FATAL_ERROR)
project(kateai VERSION 0.1)
set(KF5_DEP_VERSION "5.111.0")
find_package(ECM ${KF5_DEP_VERSION} QUIET REQUIRED NO_MODULE)
list(APPEND CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
include(KDECompilerSettings NO_POLICY_SCOPE)
include(KDEInstallDirs)
include(KDECMakeSettings)
include(KDEClangFormat)
include(KDEGitCommitHooks)
include(ECMOptionalAddSubdirectory)
include(ECMAddAppIcon)
include(ECMInstallIcons)
include(ECMDeprecationSettings)
find_package(Qt5Widgets CONFIG REQUIRED)
find_package(KF5
REQUIRED COMPONENTS
CoreAddons
GuiAddons
TextEditor
)
kcoreaddons_add_plugin(${PROJECT_NAME} INSTALL_NAMESPACE "kf5/ktexteditor")
target_link_libraries(${PROJECT_NAME} PRIVATE KF5::TextEditor)
target_sources(
${PROJECT_NAME}
PRIVATE
katecolorpickerplugin.cpp
)

302
katecolorpickerplugin.cpp Normal file
View File

@ -0,0 +1,302 @@
/*
SPDX-FileCopyrightText: 2018 Sven Brauch <mail@svenbrauch.de>
SPDX-FileCopyrightText: 2018 Michal Srb <michalsrb@gmail.com>
SPDX-FileCopyrightText: 2020 Jan Paul Batrina <jpmbatrina01@gmail.com>
SPDX-FileCopyrightText: 2021 Dominik Haumann <dhaumann@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "katecolorpickerplugin.h"
#include <KConfigGroup>
#include <KLocalizedString>
#include <KPluginFactory>
#include <KSharedConfig>
#include <KTextEditor/Document>
#include <KTextEditor/View>
#include <KTextEditor/InlineNoteInterface>
#include <QColor>
#include <QColorDialog>
#include <QFontMetricsF>
#include <QPainter>
#include <QPointer>
#include <QVariant>
ColorPickerInlineNoteProvider::ColorPickerInlineNoteProvider(KTextEditor::Document *doc)
: m_doc(doc)
{
// initialize the color regex
m_colorRegex.setPatternOptions(QRegularExpression::DontCaptureOption | QRegularExpression::CaseInsensitiveOption);
updateColorMatchingCriteria();
const auto views = m_doc->views();
for (auto view : views) {
qobject_cast<KTextEditor::InlineNoteInterface *>(view)->registerInlineNoteProvider(this);
}
connect(m_doc, &KTextEditor::Document::viewCreated, this, [this](KTextEditor::Document *, KTextEditor::View *view) {
qobject_cast<KTextEditor::InlineNoteInterface *>(view)->registerInlineNoteProvider(this);
});
auto lineChanged = [this](const int line) {
if (m_startChangedLines == -1 || m_endChangedLines == -1) {
m_startChangedLines = line;
// changed line is directly above/below the previous changed line, so we just update them
} else if (line == m_endChangedLines) { // handled below. Condition added here to avoid fallthrough
} else if (line == m_startChangedLines - 1) {
m_startChangedLines = line;
} else if (line < m_startChangedLines || line > m_endChangedLines) {
// changed line is outside the range of previous changes. Change proably skipped lines
updateNotes(m_startChangedLines, m_endChangedLines);
m_startChangedLines = line;
m_endChangedLines = -1;
}
m_endChangedLines = line >= m_endChangedLines ? line + 1 : m_endChangedLines;
};
// textInserted and textRemoved are emitted per line, then the last line is followed by a textChanged signal
connect(m_doc, &KTextEditor::Document::textInserted, this, [lineChanged](KTextEditor::Document *, const KTextEditor::Cursor &cur, const QString &) {
lineChanged(cur.line());
});
connect(m_doc, &KTextEditor::Document::textRemoved, this, [lineChanged](KTextEditor::Document *, const KTextEditor::Range &range, const QString &) {
lineChanged(range.start().line());
});
connect(m_doc, &KTextEditor::Document::textChanged, this, [this](KTextEditor::Document *) {
int newNumLines = m_doc->lines();
if (m_startChangedLines == -1) {
// textChanged not preceded by textInserted or textRemoved. This probably means that either:
// *empty line(s) were inserted/removed (TODO: Update only the lines directly below the removed/inserted empty line(s))
// *the document is newly opened so we update all lines
updateNotes();
} else {
if (m_previousNumLines != newNumLines) {
// either whole line(s) were removed or inserted. We update all lines (even those that are now non-existent) below m_startChangedLines
m_endChangedLines = newNumLines > m_previousNumLines ? newNumLines : m_previousNumLines;
}
updateNotes(m_startChangedLines, m_endChangedLines);
}
m_startChangedLines = -1;
m_endChangedLines = -1;
m_previousNumLines = newNumLines;
});
updateNotes();
}
ColorPickerInlineNoteProvider::~ColorPickerInlineNoteProvider()
{
QPointer<KTextEditor::Document> doc = m_doc;
if (doc) {
const auto views = m_doc->views();
for (auto view : views) {
qobject_cast<KTextEditor::InlineNoteInterface *>(view)->unregisterInlineNoteProvider(this);
}
}
}
void ColorPickerInlineNoteProvider::updateColorMatchingCriteria()
{
KConfigGroup config(KSharedConfig::openConfig(), "ColorPicker");
m_matchHexLengths = config.readEntry("HexLengths", QList<int>{12, 9, 6, 3}).toVector();
m_putPreviewAfterColor = config.readEntry("PreviewAfterColor", true);
m_matchNamedColors = config.readEntry("NamedColors", false);
QString colorRegex;
if (m_matchHexLengths.size() > 0) {
colorRegex += QLatin1String("(#[[:xdigit:]]{3,12})");
}
if (m_matchNamedColors) {
if (!colorRegex.isEmpty()) {
colorRegex += QLatin1Char('|');
}
// shortest and longest colors have 3 (e.g. red) and 20 (lightgoldenrodyellow) characters respectively
colorRegex += QLatin1String("((?<![\\w])[a-z]{3,20})");
}
if (!colorRegex.isEmpty()) {
colorRegex = QStringLiteral("(?<![-])(%1)(?![-\\w])").arg(colorRegex);
} else {
// No matching criteria enabled. Set regex to negative lookahead to match nothing.
colorRegex = QLatin1String("(?!)");
}
m_colorRegex.setPattern(colorRegex);
}
void ColorPickerInlineNoteProvider::updateNotes(int startLine, int endLine)
{
if (m_colorNoteIndices.isEmpty()) {
return;
}
startLine = startLine < -1 ? -1 : startLine;
if (startLine == -1) {
startLine = 0;
// we use whichever of newNumLines and m_previousNumLines are longer so that note indices for non-existent lines are also removed
const int lastLine = m_doc->lines();
endLine = lastLine > m_previousNumLines ? lastLine : m_previousNumLines;
}
if (endLine == -1) {
endLine = startLine;
}
for (int line = startLine; line < endLine; ++line) {
int removed = m_colorNoteIndices.remove(line);
if (removed != 0) {
Q_EMIT inlineNotesChanged(line);
}
}
}
QVector<int> ColorPickerInlineNoteProvider::inlineNotes(int line) const
{
if (!m_colorNoteIndices.contains(line)) {
const QString lineText = m_doc->line(line);
auto matchIter = m_colorRegex.globalMatch(lineText);
while (matchIter.hasNext()) {
const auto match = matchIter.next();
if (!QColor(match.captured()).isValid()) {
continue;
}
if (lineText.at(match.capturedStart()) == QLatin1Char('#') && !m_matchHexLengths.contains(match.capturedLength() - 1)) {
// matching for this hex color format is disabled
continue;
}
int start = match.capturedStart();
int end = start + match.capturedLength();
if (m_putPreviewAfterColor) {
start = end;
end = match.capturedStart();
}
auto &colorIndices = m_colorNoteIndices[line];
colorIndices.colorNoteIndices.append(start);
colorIndices.otherColorIndices.append(end);
}
}
return m_colorNoteIndices[line].colorNoteIndices;
}
QSize ColorPickerInlineNoteProvider::inlineNoteSize(const KTextEditor::InlineNote &note) const
{
return QSize(note.lineHeight() - 1, note.lineHeight() - 1);
}
void ColorPickerInlineNoteProvider::paintInlineNote(const KTextEditor::InlineNote &note, QPainter &painter) const
{
const auto line = note.position().line();
auto colorEnd = note.position().column();
const QVector<int> &colorNoteIndices = m_colorNoteIndices[line].colorNoteIndices;
// Since the colorNoteIndices are inserted in left-to-right (increasing) order in inlineNotes(), we can use binary search to find the index (or color note
// number) for the line
const int colorNoteNumber = std::lower_bound(colorNoteIndices.cbegin(), colorNoteIndices.cend(), colorEnd) - colorNoteIndices.cbegin();
auto colorStart = m_colorNoteIndices[line].otherColorIndices[colorNoteNumber];
if (colorStart > colorEnd) {
colorEnd = colorStart;
colorStart = note.position().column();
}
const auto color = QColor(m_doc->text({line, colorStart, line, colorEnd}));
// ensure that the border color is always visible
QColor penColor = color;
penColor.setAlpha(255);
painter.setPen(penColor.value() < 128 ? penColor.lighter(150) : penColor.darker(150));
painter.setBrush(color);
painter.setRenderHint(QPainter::Antialiasing, false);
const QFontMetricsF fm(note.font());
const int inc = note.underMouse() ? 1 : 0;
const int ascent = fm.ascent();
const int margin = (note.lineHeight() - ascent) / 2;
painter.drawRect(margin - inc, margin - inc, ascent - 1 + 2 * inc, ascent - 1 + 2 * inc);
}
void ColorPickerInlineNoteProvider::inlineNoteActivated(const KTextEditor::InlineNote &note, Qt::MouseButtons, const QPoint &)
{
const auto line = note.position().line();
auto colorEnd = note.position().column();
const QVector<int> &colorNoteIndices = m_colorNoteIndices[line].colorNoteIndices;
// Since the colorNoteIndices are inserted in left-to-right (increasing) order in inlineNotes, we can use binary search to find the index (or color note
// number) for the line
const int colorNoteNumber = std::lower_bound(colorNoteIndices.cbegin(), colorNoteIndices.cend(), colorEnd) - colorNoteIndices.cbegin();
auto colorStart = m_colorNoteIndices[line].otherColorIndices[colorNoteNumber];
if (colorStart > colorEnd) {
colorEnd = colorStart;
colorStart = note.position().column();
}
const auto oldColor = QColor(m_doc->text({line, colorStart, line, colorEnd}));
QColorDialog::ColorDialogOptions dialogOptions = QColorDialog::ShowAlphaChannel;
QString title = i18n("Select Color (Hex output)");
if (!m_doc->isReadWrite()) {
dialogOptions |= QColorDialog::NoButtons;
title = i18n("View Color [Read only]");
}
const QColor newColor = QColorDialog::getColor(oldColor, const_cast<KTextEditor::View *>(note.view()), title, dialogOptions);
if (!newColor.isValid()) {
return;
}
// include alpha channel if the new color has transparency or the old color included transparency (#AARRGGBB, 9 hex digits)
auto colorNameFormat = (newColor.alpha() != 255 || colorEnd - colorStart == 9) ? QColor::HexArgb : QColor::HexRgb;
m_doc->replaceText({line, colorStart, line, colorEnd}, newColor.name(colorNameFormat));
}
K_PLUGIN_FACTORY_WITH_JSON(KateColorPickerPluginFactory, "katecolorpickerplugin.json", registerPlugin<KateColorPickerPlugin>();)
KateColorPickerPlugin::KateColorPickerPlugin(QObject *parent, const QList<QVariant> &)
: KTextEditor::Plugin(parent)
{
}
KateColorPickerPlugin::~KateColorPickerPlugin() = default;
QObject *KateColorPickerPlugin::createView(KTextEditor::MainWindow *mainWindow)
{
m_mainWindow = mainWindow;
const auto views = m_mainWindow->views();
for (auto view : views) {
addDocument(view->document());
}
connect(m_mainWindow, &KTextEditor::MainWindow::viewCreated, this, [this](KTextEditor::View *view) {
addDocument(view->document());
});
return nullptr;
}
void KateColorPickerPlugin::addDocument(KTextEditor::Document *doc)
{
if (m_inlineColorNoteProviders.find(doc) == m_inlineColorNoteProviders.end()) {
m_inlineColorNoteProviders.emplace(doc, new ColorPickerInlineNoteProvider(doc));
}
connect(doc, &KTextEditor::Document::aboutToClose, this, [this, doc]() {
m_inlineColorNoteProviders.erase(doc);
});
}
void KateColorPickerPlugin::readConfig()
{
for (const auto &[doc, colorNoteProvider] : m_inlineColorNoteProviders) {
Q_UNUSED(doc)
colorNoteProvider->updateColorMatchingCriteria();
colorNoteProvider->updateNotes();
}
}
#include "katecolorpickerplugin.moc"
#include "moc_katecolorpickerplugin.cpp"

81
katecolorpickerplugin.h Normal file
View File

@ -0,0 +1,81 @@
/*
SPDX-FileCopyrightText: 2018 Sven Brauch <mail@svenbrauch.de>
SPDX-FileCopyrightText: 2018 Michal Srb <michalsrb@gmail.com>
SPDX-FileCopyrightText: 2020 Jan Paul Batrina <jpmbatrina01@gmail.com>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#pragma once
#include <unordered_map>
#include <KTextEditor/ConfigPage>
#include <KTextEditor/InlineNoteProvider>
#include <KTextEditor/MainWindow>
#include <KTextEditor/Plugin>
#include <QHash>
#include <QList>
#include <QRegularExpression>
#include <QVariant>
class ColorPickerInlineNoteProvider : public KTextEditor::InlineNoteProvider
{
Q_OBJECT
public:
explicit ColorPickerInlineNoteProvider(KTextEditor::Document *doc);
~ColorPickerInlineNoteProvider() override;
void updateColorMatchingCriteria();
// if startLine == -1, update all notes. endLine is optional
void updateNotes(int startLine = -1, int endLine = -1);
QVector<int> inlineNotes(int line) const override;
QSize inlineNoteSize(const KTextEditor::InlineNote &note) const override;
void paintInlineNote(const KTextEditor::InlineNote &note, QPainter &painter) const override;
void inlineNoteActivated(const KTextEditor::InlineNote &note, Qt::MouseButtons buttons, const QPoint &globalPos) override;
private:
KTextEditor::Document *m_doc;
int m_startChangedLines = -1;
int m_endChangedLines = -1;
int m_previousNumLines = -1;
struct ColorIndices {
// When m_putPreviewAfterColor is true, otherColorIndices holds the starting color indices while colorNoteIndices holds the end color indices (and vice
// versa) colorNoteIndices[i] corresponds to otherColorIndices[i]
QVector<int> colorNoteIndices;
QVector<int> otherColorIndices;
};
// mutable is used here since InlineNoteProvider::inlineNotes() is const only, and we update the notes lazily (only when inlineNotes() is called)
mutable QHash<int, ColorIndices> m_colorNoteIndices;
QRegularExpression m_colorRegex;
QVector<int> m_matchHexLengths;
bool m_putPreviewAfterColor;
bool m_matchNamedColors;
};
class KateColorPickerPlugin : public KTextEditor::Plugin
{
Q_OBJECT
public:
explicit KateColorPickerPlugin(QObject *parent = nullptr, const QList<QVariant> & = QList<QVariant>());
~KateColorPickerPlugin() override;
QObject *createView(KTextEditor::MainWindow *mainWindow) override;
void readConfig();
private:
void addDocument(KTextEditor::Document *doc);
int configPages() const override
{
return 0;
}
KTextEditor::MainWindow *m_mainWindow;
std::unordered_map<KTextEditor::Document *, std::unique_ptr<ColorPickerInlineNoteProvider>> m_inlineColorNoteProviders;
};

View File

@ -0,0 +1,75 @@
{
"KPlugin": {
"Description": "Adds an inline Color preview/picker to colors in the text (e.g. #FFFFFF, white)",
"Description[az]": "Mətndəki sətirdaxili rəng önizləməsi əlavə edir/mətndəki rəngləri seçə vasitəsi (məs., #FFFFFF, ağ rəng)",
"Description[bg]": "Добавя възможност за преглед и избор на цвят в текста (напр.. #FFFFFF, бял)",
"Description[ca@valencia]": "Afig una vista prèvia/selector de colors inclòs per als colors del text (p. ex., #FFFFFF, blanc)",
"Description[ca]": "Afegeix una vista prèvia/selector de colors inclòs per als colors del text (p. ex., #FFFFFF, blanc)",
"Description[de]": "Fügt eine Farbvorschau/Farbauswahl für Farben wie #FFFFFF, white am Zeilenende ein",
"Description[el]": "Προσθέτει ένα εμβόλιμο εργαλείο προεπισκόπησης/επιλογής χρώματος στα χρώματα του κειμένου (π.χ. #FFFFFF, λευκό)",
"Description[en_GB]": "Adds an inline Colour preview/picker to colours in the text (e.g. #FFFFFF, white)",
"Description[eo]": "Aldonas enlinian Koloran antaŭrigardon/elektilon al koloroj en la teksto (ekz. #FFFFFF, blanka)",
"Description[es]": "Añade una vista previa/selector de color en línea a los colores del texto (por ejemplo: #FFFFFF, blanco)",
"Description[eu]": "Testuko koloreei lerro barruko Kolore aurreikuspegi/hautatzaile bat gehitzen die (adib. #FFFFFF, zuria)",
"Description[fi]": "Lisää tekstin väreille (esim. #FFFFFF, white) upotetun väriesikatselun/-valitsimen",
"Description[fr]": "Ajoute un aperçu intégré / sélecteur de couleurs pour les couleurs dans le texte (par exemple #FFFFFF, blanc)",
"Description[gl]": "Engade un selector e vista previa de cores in situ no texto (p. ex. #FFFFFF, white).",
"Description[hu]": "Beágyazott színválasztó a szövegben lévő színekhez (például #FFFFFF, fehér)",
"Description[ia]": "Adde un vista preliminar/selectionator de Color a lolores in le texto (p.ex. #FFFFFF, blanco)",
"Description[ie]": "Adjunter un visor e selector de colores in li textu (p.ex. #FFFFFF, white)",
"Description[it]": "Aggiunge nelle righe del testo un'anteprima colore/selettore (ad es. #FFFFFF, bianco)",
"Description[ka]": "ხაზშივე ტექსტის ფერის გადახედვის/ამრჩევის დამატება (მაგ: #FFFFFF, თეთრი)",
"Description[ko]": "문자열 내 인라인 색상 미리 보기/선택기 추가(예: #FFFFFF, 흰색)",
"Description[nl]": "Voegt een inline kleurenvoorbeeld/kiezer toe aan kleuren in de tekst (bijv. #FFFFFF, wit)",
"Description[pl]": "Dodaje wbudowany wybierak barwy dla barw tekstu (np. #FFFFFF, biały)",
"Description[pt]": "Adiciona uma antevisão/selector de cores incorporado para as cores no texto (p.ex. #FFFFFF, white)",
"Description[ru]": "Добавление в текст встроенного средства предварительного просмотра и выбора цвета (например, #FFFFFF, white (белый))",
"Description[sk]": "Pridá vložený náhľad/výber farieb na farby v texte (napr. #FFFFFF,biela)",
"Description[sl]": "Doda besedilu vrstni predogled/izbirnik barv (npr. #FFFFFF, bela)",
"Description[sv]": "Lägger till en färggranskare/färghämtare på plats i texten (t.ex. #FFFFFF, vit)",
"Description[tr]": "Metindeki renklere satır içi renk önizleyici/seçici ekler (örn. #FFFFFF, beyaz)",
"Description[uk]": "Додає вбудовану панель попереднього перегляду і піпетку для кольорів у тексті (наприклад, #FFFFFF, білого)",
"Description[vi]": "Thêm một bộ xem thử / nhặt màu tại chỗ cho các màu trong văn bản (vd. #FFFFFF, trắng)",
"Description[x-test]": "xxAdds an inline Color preview/picker to colors in the text (e.g. #FFFFFF, white)xx",
"Description[zh_CN]": "在文本中添加内联的颜色预览/拾色器 (例如 #FFFFFF, 白色)",
"Name": "Color Picker",
"Name[ar]": "منتق الألوان",
"Name[az]": "Rəng seçici",
"Name[bg]": "Избиране на цвят",
"Name[ca@valencia]": "Selector de color",
"Name[ca]": "Selector de color",
"Name[cs]": "Kapátko",
"Name[de]": "Farbauswahl",
"Name[el]": "Επιλογέας χρωμάτων",
"Name[en_GB]": "Colour Picker",
"Name[eo]": "Kolorelektilo",
"Name[es]": "Selector de color",
"Name[eu]": "Kolore hautatzailea",
"Name[fi]": "Värivalinta",
"Name[fr]": "Sélecteur de couleurs",
"Name[gl]": "Selector de cores",
"Name[hu]": "Színválasztó",
"Name[ia]": "Selectionator de color",
"Name[ie]": "Selector de color",
"Name[it]": "Selettore di colore",
"Name[ka]": "ფერის ამრჩევი",
"Name[ko]": "색상 선택기",
"Name[my]": "အရောင်ရွေးကိရိယာ",
"Name[nl]": "Kleurenkiezer",
"Name[pl]": "Wybierak barwy",
"Name[pt]": "Selector de Cores",
"Name[pt_BR]": "Seletor de cores",
"Name[ru]": "Выбор цвета",
"Name[sk]": "Výber farby",
"Name[sl]": "Izbirnik barv",
"Name[sv]": "Färgväljare",
"Name[tr]": "Renk Seçicisi",
"Name[uk]": "Піпетка",
"Name[vi]": "Trình nhặt màu",
"Name[x-test]": "xxColor Pickerxx",
"Name[zh_CN]": "拾色器",
"ServiceTypes": [
"KTextEditor/Plugin"
]
}
}