Page Menu
Home
Phabricator (Chris)
Search
Configure Global Search
Log In
Files
F103977
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
89 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/texteditor/AbstractCharacterBasedApplication.cpp b/src/texteditor/AbstractCharacterBasedApplication.cpp
index 648efdc..60c51a7 100644
--- a/src/texteditor/AbstractCharacterBasedApplication.cpp
+++ b/src/texteditor/AbstractCharacterBasedApplication.cpp
@@ -1,2637 +1,2638 @@
#include <memory>
#include <memory>
#include "AbstractCharacterBasedApplication.h"
#include "UnicodeWidth.h"
#include "unicode.h"
#include <QApplication>
#include <QClipboard>
#include <QDebug>
#include <QFile>
#include <QTextCodec>
#include <memory>
using WriteMode = AbstractCharacterBasedApplication::WriteMode;
using FormattedLine = AbstractCharacterBasedApplication::FormattedLine;
class EsccapeSequence {
private:
int offset = 0;
unsigned char data[100];
int color_fg = -1;
int color_bg = -1;
public:
bool isActive() const
{
return offset > 0;
}
void write(char c)
{
if (c == 0x1b) {
data[0] = c;
offset = 1;
return;
}
data[offset] = c;
if (offset > 0) {
if (c == 'm') {
if (data[1] == '[' && isdigit(data[2]) && isdigit(data[3])) {
data[offset] = 0;
if (data[2] == '3') {
color_fg = atoi((char const *)data + 3);
if (color_fg == 9) {
color_fg = -1;
}
}
if (data[2] == '4') {
color_bg = atoi((char const *)data + 3);
if (color_bg == 9) {
color_bg = -1;
}
}
}
offset = 0;
return;
}
if (offset + 1 < (int)sizeof(data)) {
offset++;
}
}
}
int fg_color_code() const
{
return color_fg == 9 ? -1 : color_fg;
}
int bg_color_code() const
{
return color_bg == 9 ? -1 : color_bg;
}
};
struct AbstractCharacterBasedApplication::Private {
bool is_changed = false;
bool is_quit_enabled = false;
bool is_open_enabled = false;
bool is_save_enabled = false;
bool is_toggle_selection_anchor_enabled = true;
bool is_read_only = false;
bool is_terminal_mode = false;
bool is_cursor_visible = true;
State state = State::Normal;
int header_line = 0;
int footer_line = 0;
int screen_width = 80;
int screen_height = 24;
bool auto_layout = false;
QString recently_used_path;
bool show_line_number = true;
int left_margin = AbstractCharacterBasedApplication::LEFT_MARGIN;
QString dialog_title;
QString dialog_value;
std::vector<Character> screen;
std::vector<uint8_t> line_flags;
int parsed_row_index = -1;
int parsed_col_index = -1;
bool parsed_for_edit = false;
QByteArray parsed_line;
std::vector<AbstractCharacterBasedApplication::Char> prepared_current_line;
QList<Document::CharAttr_> syntax_table;
bool dialog_mode = false;
DialogHandler dialog_handler;
bool is_painting_suppressed = false;
int valid_line_index = -1;
int line_margin = 3;
WriteMode write_mode = WriteMode::Insert;
QTextCodec *text_codec = nullptr;
Qt::KeyboardModifiers keyboard_modifiers = Qt::KeyboardModifier::NoModifier;
bool ctrl_modifier = false;
bool shift_modifier = false;
EsccapeSequence escape_sequence;
bool cursor_moved_by_mouse = false;
};
AbstractCharacterBasedApplication::AbstractCharacterBasedApplication()
: m(new Private)
{
}
AbstractCharacterBasedApplication::~AbstractCharacterBasedApplication()
{
delete m;
}
void AbstractCharacterBasedApplication::setModifierKeys(Qt::KeyboardModifiers keymod)
{
m->keyboard_modifiers = keymod;
m->ctrl_modifier = m->keyboard_modifiers & Qt::ControlModifier;
m->shift_modifier = m->keyboard_modifiers & Qt::ShiftModifier;
}
void AbstractCharacterBasedApplication::clearShiftModifier()
{
m->shift_modifier = false;
}
bool AbstractCharacterBasedApplication::isControlModifierPressed() const
{
return m->ctrl_modifier;
}
bool AbstractCharacterBasedApplication::isShiftModifierPressed() const
{
return m->shift_modifier;
}
void AbstractCharacterBasedApplication::setTextCodec(QTextCodec *codec)
{
m->text_codec = codec;
clearParsedLine();
invalidateArea();
makeBuffer();
}
void AbstractCharacterBasedApplication::setAutoLayout(bool f)
{
m->auto_layout = f;
layoutEditor();
}
void AbstractCharacterBasedApplication::showHeader(bool f)
{
m->header_line = f ? 1 : 0;
layoutEditor();
}
void AbstractCharacterBasedApplication::showFooter(bool f)
{
m->footer_line = f ? 1 : 0;
layoutEditor();
}
void AbstractCharacterBasedApplication::showLineNumber(bool show, int left_margin)
{
m->show_line_number = show;
m->left_margin = left_margin;
}
void AbstractCharacterBasedApplication::setCursorVisible(bool show)
{
m->is_cursor_visible = show;
}
bool AbstractCharacterBasedApplication::isCursorVisible()
{
return m->is_cursor_visible;
}
void AbstractCharacterBasedApplication::retrieveLastText(std::vector<char> *out, int maxlen) const
{
const_cast<AbstractCharacterBasedApplication *>(this)->document()->retrieveLastText(out, maxlen);
}
bool AbstractCharacterBasedApplication::isChanged() const
{
return m->is_changed;
}
void AbstractCharacterBasedApplication::setChanged(bool f)
{
m->is_changed = f;
}
int AbstractCharacterBasedApplication::leftMargin_() const
{
return m->left_margin;
}
void AbstractCharacterBasedApplication::setRecentlyUsedPath(QString const &path)
{
m->recently_used_path = path;
}
QString AbstractCharacterBasedApplication::recentlyUsedPath()
{
return m->recently_used_path;
}
std::vector<AbstractCharacterBasedApplication::Character> *AbstractCharacterBasedApplication::char_screen()
{
return &m->screen;
}
const std::vector<AbstractCharacterBasedApplication::Character> *AbstractCharacterBasedApplication::char_screen() const
{
return &m->screen;
}
std::vector<uint8_t> *AbstractCharacterBasedApplication::line_flags()
{
return &m->line_flags;
}
void AbstractCharacterBasedApplication::makeBuffer()
{
int w = screenWidth();
int h = screenHeight();
int size = w * h;
m->screen.resize(size);
std::fill(m->screen.begin(), m->screen.end(), Character());
m->line_flags.resize(h);
}
void AbstractCharacterBasedApplication::layoutEditor()
{
makeBuffer();
editor_cx->viewport_org_x = leftMargin_();
editor_cx->viewport_org_y = m->header_line;
editor_cx->viewport_width = screenWidth() - cx()->viewport_org_x;
editor_cx->viewport_height = screenHeight() - (m->header_line + m->footer_line);
}
void AbstractCharacterBasedApplication::initEditor()
{
editor_cx = std::make_shared<TextEditorContext>();
layoutEditor();
}
bool AbstractCharacterBasedApplication::isLineNumberVisible() const
{
return m->show_line_number;
}
int AbstractCharacterBasedApplication::charWidth(uint32_t c)
{
return UnicodeWidth::width(UnicodeWidth::type(c));
}
QList<FormattedLine> AbstractCharacterBasedApplication::formatLine(Document::Line const &line, int tab_span, int anchor_a, int anchor_b) const
{
QByteArray ba;
if (m->text_codec) {
ba = m->text_codec->toUnicode(line.text).toUtf8();
} else {
ba = line.text;
}
std::vector<ushort> vec;
vec.reserve(ba.size() + 100);
size_t len = ba.size();
QList<FormattedLine> res;
int col = 0;
int col_start = col;
bool flag_a = false;
bool flag_b = false;
auto Color = [&](size_t offset, size_t *next_offset){
int i = findSyntax(&m->syntax_table, offset);
if (i < m->syntax_table.size() && m->syntax_table[i].offset <= offset) {
if (next_offset) {
if (i + 1 < m->syntax_table.size()) {
*next_offset = m->syntax_table[i + 1].offset;
} else {
*next_offset = -1;
}
}
static int color[] = {
0x000000,
0x0000ff,
0x00ff00,
0x00ffff,
0xff0000,
0xff00ff,
0xffff00,
0xffffff,
};
i = m->syntax_table[i].color;
if (i >= 0 && i < 8) {
return color[i];
}
}
if (next_offset) {
*next_offset = -1;
}
return 0;
};
auto Flush = [&](size_t offset, size_t *next_offset){
if (!vec.empty()) {
int atts = 0;
atts |= Color(offset, next_offset);
if (anchor_a >= 0 || anchor_b >= 0) {
if ((anchor_a < 0 || col_start >= anchor_a) && (anchor_b == -1 || col_start < anchor_b)) {
atts |= FormattedLine::Selected;
}
}
ushort const *left = &vec[0];
ushort const *right = left + vec.size();
while (left < right && (right[-1] == '\r' || right[-1] == '\n')) right--;
if (left < right) {
res.push_back(FormattedLine(QString::fromUtf16(left, right - left), atts));
}
vec.clear();
} else {
*next_offset = -1;
}
col_start = col;
};
size_t offset = 0;
size_t next_offset = -1;
if (len > 0) {
{
size_t o = line.byte_offset;
int i = findSyntax(&m->syntax_table, o);
if (i < m->syntax_table.size() && m->syntax_table[i].offset <= o) {
if (i + 1 < m->syntax_table.size()) {
o = m->syntax_table[i + 1].offset;
if (o != (size_t)-1) {
next_offset = o;
}
}
}
}
utf8 u8(ba.data(), len);
u8.to_utf32([&](uint32_t c){
if (line.byte_offset + u8.offset() == next_offset) {
Flush(line.byte_offset + offset, &next_offset);
offset += u8.offset();
}
if (c == '\t') {
do {
vec.push_back(' ');
col++;
} while (col % tab_span != 0);
} else if (c < ' ') {
// nop
} else {
int cw = charWidth(c);
if (c < 0xffff) {
vec.push_back(c);
} else {
int a = c >> 16;
if (a > 0 && a <= 0x20) {
a--;
int b = c & 0x03ff;
a = (a << 6) | ((c >> 10) & 0x003f);
a |= 0xd800;
b |= 0xdc00;
vec.push_back(a);
vec.push_back(b);
}
}
col += cw;
}
if ((anchor_a >= 0 || anchor_b >= 0) && anchor_a != anchor_b) {
if (!flag_a && col >= anchor_a) {
Flush(line.byte_offset + offset, &next_offset);
flag_a = true;
}
if (!flag_b && col >= anchor_b) {
Flush(line.byte_offset + offset, &next_offset);
flag_b = true;
}
}
return true;
});
}
Flush(line.byte_offset + offset, &next_offset);
return res;
}
bool AbstractCharacterBasedApplication::isValidRowIndex(int row_index) const
{
return row_index >= 0 && row_index < engine()->document.lines.size();
}
QList<AbstractCharacterBasedApplication::FormattedLine> AbstractCharacterBasedApplication::formatLine2(int row_index) const
{
QList<FormattedLine> formatted_lines;
if (isValidRowIndex(row_index)) {
Document::Line const *line = &engine()->document.lines[row_index];
formatted_lines = formatLine(*line, cx()->tab_span);
}
return formatted_lines;
}
QByteArray AbstractCharacterBasedApplication::fetchLine(int row) const
{
QByteArray line;
int lines = documentLines();
if (row >= 0 && row < lines) {
line = cx()->engine->document.lines[row].text;
}
return line;
}
void AbstractCharacterBasedApplication::fetchCurrentLine() const
{
int row = cx()->current_row;
m->parsed_line = fetchLine(row);
m->parsed_row_index = row;
}
void AbstractCharacterBasedApplication::clearParsedLine()
{
m->parsed_row_index = -1;
m->parsed_for_edit = false;
m->parsed_line.clear();
}
int AbstractCharacterBasedApplication::cursorX() const
{
return cx()->current_col - cx()->scroll_col_pos;
}
int AbstractCharacterBasedApplication::cursorY() const
{
return cx()->current_row - cx()->scroll_row_pos;
}
int AbstractCharacterBasedApplication::screenWidth() const
{
return m->screen_width;
}
int AbstractCharacterBasedApplication::screenHeight() const
{
return m->screen_height;
}
void AbstractCharacterBasedApplication::setScreenSize(int w, int h, bool update_layout)
{
m->screen_width = w;
m->screen_height = h;
if (update_layout) {
layoutEditor();
}
}
bool AbstractCharacterBasedApplication::isPaintingSuppressed() const
{
return m->is_painting_suppressed;
}
void AbstractCharacterBasedApplication::setPaintingSuppressed(bool f)
{
m->is_painting_suppressed = f;
}
void AbstractCharacterBasedApplication::commitLine(std::vector<Char> const &vec)
{
if (isReadOnly()) return;
QByteArray ba;
if (!vec.empty()){
std::vector<uint32_t> v;
for (Char const &c : vec) {
v.push_back(c.unicode);
}
utf32 u32(&v[0], v.size());
u32.to_utf8([&](char c, int pos){
(void)pos;
ba.push_back(c);
return true;
});
}
if (m->parsed_row_index == 0 && engine()->document.lines.empty()) {
Document::Line newline;
engine()->document.lines.push_back(newline);
}
Document::Line *line = &engine()->document.lines[m->parsed_row_index];
if (m->parsed_row_index == 0) {
line->byte_offset = 0;
line->line_number = (line->type == Document::Line::Unknown) ? 0 : 1;
}
line->text = ba;
if (m->valid_line_index > m->parsed_row_index) {
m->valid_line_index = m->parsed_row_index;
}
int y = m->parsed_row_index - cx()->scroll_row_pos + cx()->viewport_org_y;
if (y >= 0 && y < (int)m->line_flags.size()) {
m->line_flags[y] |= LineChanged;
}
}
void AbstractCharacterBasedApplication::parseLine(std::vector<Char> *vec, int increase_hint, bool force)
{
if (force) {
clearParsedLine();
}
if (force || !m->parsed_for_edit) {
fetchCurrentLine();
m->parsed_col_index = internalParseLine(m->parsed_line, vec ? vec : &m->prepared_current_line, increase_hint);
m->parsed_for_edit = true;
} else {
if (vec) {
*vec = m->prepared_current_line;
}
}
}
int AbstractCharacterBasedApplication::parseLine2(int row, std::vector<Char> *vec) const
{
QByteArray line = fetchLine(row);
int index = internalParseLine(line, vec, 0);
return index;
}
QByteArray AbstractCharacterBasedApplication::parsedLine() const
{
return m->parsed_line;
}
bool AbstractCharacterBasedApplication::isCurrentLineWritable() const
{
if (isReadOnly()) return false;
int row = cx()->current_row;
if (row >= 0 && row < cx()->engine->document.lines.size()) {
if (cx()->engine->document.lines[row].type != Document::Line::Unknown) {
return true;
}
}
return false;
}
int AbstractCharacterBasedApplication::editorViewportWidth() const
{
return cx()->viewport_width;
}
int AbstractCharacterBasedApplication::editorViewportHeight() const
{
return cx()->viewport_height;
}
int AbstractCharacterBasedApplication::print(int x, int y, QString const &text, const AbstractCharacterBasedApplication::Option &opt)
{
CharAttr attr = opt.char_attr;
if (opt.char_attr.flags & CharAttr::Selected) {
attr.index = CharAttr::Invert;
}
if (opt.char_attr.flags & CharAttr::CurrentLine) {
attr.index = CharAttr::Hilite;
}
const int screen_w = screenWidth();
const int screen_h = screenHeight();
int x_start = 0;
int y_start = 0;
int x_limit = screen_w;
int y_limit = screen_h;
if (!opt.clip.isNull()) {
x_start = opt.clip.x();
y_start = opt.clip.y();
x_limit = x_start + opt.clip.width();
y_limit = y_start + opt.clip.height();
}
if (y >= y_start && y < y_limit) {
bool changed = false;
int x2 = x;
int y2 = y;
if (text.isEmpty()) {
changed = true; // set changed flag if text is empty
} else {
for (int i = 0; i < text.size(); i++) {
ushort c = text.utf16()[i];
if (c < ' ' || c == 0x7f) continue;
int cw = charWidth(c);
if (x2 + cw > x_limit) {
break;
}
for (int j = 0; j < cw; j++) {
if (x2 >= x_start && x2 < screen_w) {
int o = y2 * screen_w + x2;
CharAttr a;
if (j == 0) {
a = attr;
} else {
c = -1;
}
if (c != m->screen[o].c || a != m->screen[o].a) {
m->screen[o].c = c;
m->screen[o].a = a;
changed = true;
}
}
x2++;
}
}
}
if (changed) {
m->line_flags[y] |= LineChanged;
}
x = x2;
}
return x;
}
void AbstractCharacterBasedApplication::initEngine(std::shared_ptr<TextEditorContext> const &cx)
{
cx->engine = std::make_shared<TextEditorEngine>();
}
TextEditorContext *AbstractCharacterBasedApplication::cx()
{
if (dialog_cx) {
if (!dialog_cx->engine) {
initEngine(dialog_cx);
}
return dialog_cx.get();
}
if (!editor_cx->engine) {
initEngine(editor_cx);
}
return editor_cx.get();
}
const TextEditorContext *AbstractCharacterBasedApplication::cx() const
{
return const_cast<AbstractCharacterBasedApplication *>(this)->cx();
}
TextEditorEnginePtr AbstractCharacterBasedApplication::engine() const
{
Q_ASSERT(cx()->engine);
return cx()->engine;
}
void AbstractCharacterBasedApplication::setTextEditorEngine(TextEditorEnginePtr const &e)
{
cx()->engine = e;
}
void AbstractCharacterBasedApplication::setDocument(QList<Document::Line> const *source)
{
document()->lines = *source;
}
void AbstractCharacterBasedApplication::openFile(QString const &path)
{
document()->lines.clear();
QFile file(path);
if (file.open(QFile::ReadOnly)) {
size_t offset = 0;
QString line;
unsigned int linenum = 0;
while (!file.atEnd()) {
linenum++;
Document::Line line(file.readLine());
line.byte_offset = offset;
line.type = Document::Line::Normal;
line.line_number = linenum;
document()->lines.push_back(line);
offset += line.text.size();
}
int n = line.size();
if (n > 0) {
ushort const *p = line.utf16();
ushort c = p[n - 1];
if (c == '\r' || c == '\n') {
document()->lines.push_back(Document::Line(QByteArray()));
}
}
m->valid_line_index = (int)document()->lines.size();
setRecentlyUsedPath(path);
}
scrollToTop();
}
void AbstractCharacterBasedApplication::saveFile(QString const &path)
{
QFile file(path);
if (file.open(QFile::WriteOnly)) {
for (Document::Line const &line : document()->lines) {
file.write(line.text);
}
}
}
void AbstractCharacterBasedApplication::pressEnter()
{
deleteIfSelected();
if (isDialogMode()) {
closeDialog(true);
} else {
writeNewLine();
}
}
void AbstractCharacterBasedApplication::pressEscape()
{
if (isTerminalMode()) {
m->escape_sequence.write(0x1b);
return;
}
if (isDialogMode()) {
closeDialog(false);
return;
}
deselect();
updateVisibility(false, false, false);
}
AbstractCharacterBasedApplication::State AbstractCharacterBasedApplication::state() const
{
return m->state;
}
Document *AbstractCharacterBasedApplication::document()
{
return &engine()->document;
}
int AbstractCharacterBasedApplication::documentLines() const
{
return const_cast<AbstractCharacterBasedApplication *>(this)->document()->lines.size();
}
bool AbstractCharacterBasedApplication::isSingleLineMode() const
{
return cx()->single_line;
}
void AbstractCharacterBasedApplication::setLineMargin(int n)
{
m->line_margin = n;
}
void AbstractCharacterBasedApplication::ensureCurrentLineVisible()
{
int margin = (cx()->viewport_height >= 6 && !isSingleLineMode()) ? m->line_margin : 0;
int pos = cx()->scroll_row_pos;
int top = cx()->current_row - margin;
int bottom = cx()->current_row + 1 - editorViewportHeight() + margin;
if (pos > top) pos = top;
if (pos < bottom) pos = bottom;
if (pos < 0) pos = 0;
if (cx()->scroll_row_pos != pos) {
cx()->scroll_row_pos = pos;
invalidateArea();
}
}
int AbstractCharacterBasedApplication::decideColumnScrollPos() const
{
int x = cx()->current_col;
int w = editorViewportWidth() - RIGHT_MARGIN;
if (w < 0) w = 0;
return x > w ? (cx()->current_col - w) : 0;
}
int AbstractCharacterBasedApplication::calcVisualWidth(const Document::Line &line) const
{
QList<FormattedLine> lines = formatLine(line, cx()->tab_span);
int x = 0;
for (FormattedLine const &line : lines) {
if (line.text.isEmpty()) continue;
ushort const *ptr = line.text.utf16();
ushort const *end = ptr + line.text.size();
while (1) {
int c = -1;
if (ptr < end) {
c = *ptr;
ptr++;
}
if (c == -1 || c == '\r' || c == '\n') {
break;
}
if (c == '\t') {
// x += cx()->tab_span;
// x -= x % cx()->tab_span;
x++;
} else {
// x += charWidth(c);
x++;
}
}
}
return x;
}
void AbstractCharacterBasedApplication::clearRect(int x, int y, int w, int h)
{
int scr_w = screenWidth();
int scr_h = screenHeight();
int y0 = y;
int y1 = y + h;
if (y0 < 0) y0 = 0;
if (y1 > scr_h) y1 = scr_h;
int x0 = x;
int x1 = x + w;
if (x0 < 0) x0 = 0;
if (x1 > scr_w) x1 = scr_w;
for (int y = y0; y < y1; y++) {
for (int x = x0; x < x1; x++) {
int o = y * scr_w + x;
m->screen[o] = Character();
}
}
}
int AbstractCharacterBasedApplication::calcIndexToColumn(const std::vector<Char> &vec, int index) const
{
int col = 0;
for (int i = 0; i < index; i++) {
uint32_t c = vec.at(i).unicode;
- if (c == '\t') {
- col += cx()->tab_span;
- col -= col % cx()->tab_span;
- } else {
- col += charWidth(c);
- }
+// if (c == '\t') {
+// col += cx()->tab_span;
+// col -= col % cx()->tab_span;
+// } else {
+// col += charWidth(c);
+// }
+ col++;
}
return col;
}
void AbstractCharacterBasedApplication::savePos()
{
TextEditorContext *p = editor_cx.get();
if (p) {
p->saved_row = p->current_row;
p->saved_col = p->current_col;
p->saved_col_hint = p->current_col_hint;
}
}
void AbstractCharacterBasedApplication::restorePos()
{
TextEditorContext *p = editor_cx.get();
if (p) {
p->current_row = p->saved_row;
p->current_col = p->saved_col;
p->current_col_hint = p->saved_col_hint;
}
}
void AbstractCharacterBasedApplication::setCursorRow(int row, bool auto_scroll, bool by_mouse)
{
if (cx()->current_row == row) return;
if (isShiftModifierPressed()) {
if (selection_anchor_0.enabled == SelectionAnchor::No) {
setSelectionAnchor(SelectionAnchor::EnabledEasy, true, auto_scroll);
selection_anchor_1 = selection_anchor_0;
}
} else if (selection_anchor_0.enabled == SelectionAnchor::EnabledEasy) {
setSelectionAnchor(SelectionAnchor::No, false, auto_scroll);
}
cx()->current_row = row;
if (selection_anchor_0.enabled != SelectionAnchor::No) {
setSelectionAnchor(selection_anchor_0.enabled, true, auto_scroll);
}
m->cursor_moved_by_mouse = by_mouse;
}
void AbstractCharacterBasedApplication::setCursorCol(int col, bool auto_scroll, bool by_mouse)
{
if (cx()->current_col == col) {
cx()->current_col_hint = col;
return;
}
if (isShiftModifierPressed()) {
if (selection_anchor_0.enabled == SelectionAnchor::No) {
setSelectionAnchor(SelectionAnchor::EnabledEasy, true, auto_scroll);
selection_anchor_1 = selection_anchor_0;
}
} else if (selection_anchor_0.enabled == SelectionAnchor::EnabledEasy) {
setSelectionAnchor(SelectionAnchor::No, false, auto_scroll);
}
cx()->current_col = col;
cx()->current_col_hint = col;
if (selection_anchor_0.enabled != SelectionAnchor::No) {
setSelectionAnchor(selection_anchor_0.enabled, true, auto_scroll);
}
m->cursor_moved_by_mouse = by_mouse;
}
void AbstractCharacterBasedApplication::setCursorPos(int row, int col)
{
setCursorRow(row, false);
setCursorCol(col, false);
}
void AbstractCharacterBasedApplication::setCursorColByIndex(std::vector<Char> const &vec, int col_index)
{
int col = calcIndexToColumn(vec, col_index);
setCursorCol(col);
}
int AbstractCharacterBasedApplication::nextTabStop(int x) const
{
x += cx()->tab_span;
x -= x % cx()->tab_span;
return x;
}
void AbstractCharacterBasedApplication::editSelected(EditOperation op, std::vector<Char> *cutbuffer)
{
if (isReadOnly() && op == EditOperation::Cut) {
op = EditOperation::Copy;
}
SelectionAnchor a = selection_anchor_0;
SelectionAnchor b = selection_anchor_1;
if (!a.enabled) return;
if (!b.enabled) return;
if (a == b) return;
auto UpdateVisibility = [&](){
updateVisibility(false, false, false);
};
if (cutbuffer) {
cutbuffer->clear();
}
std::list<std::vector<Char>> cutlist;
if (a.row > b.row) {
std::swap(a, b);
} else if (a.row == b.row) {
if (a.col > b.col) {
std::swap(a, b);
}
}
int curr_row = cx()->current_row;
int curr_col = cx()->current_col;
cx()->current_row = b.row;
cx()->current_col = b.col;
if (a.row == b.row) {
std::vector<Char> vec1;
parseLine(&vec1, 0, true);
auto begin = vec1.begin() + calcColumnToIndex(a.col);
auto end = vec1.begin() + calcColumnToIndex(b.col);
if (cutbuffer) {
std::vector<Char> cut;
cut.insert(cut.end(), begin, end);
cutlist.push_back(std::move(cut));
}
if (op == EditOperation::Cut) {
vec1.erase(begin, end);
commitLine(vec1);
UpdateVisibility();
}
} else {
std::vector<Char> vec1;
parseLine(&vec1, 0, true);
{
auto begin = vec1.begin();
auto end = vec1.begin() + calcColumnToIndex(b.col);
if (cutbuffer) {
std::vector<Char> cut;
cut.insert(cut.end(), begin, end);
cutlist.push_back(std::move(cut));
}
if (op == EditOperation::Cut) {
vec1.erase(begin, end);
commitLine(vec1);
UpdateVisibility();
}
}
int n = b.row - a.row;
for (int i = 0; i < n; i++) {
QList<Document::Line> *lines = &cx()->engine->document.lines;
if (cutbuffer && i > 0) {
cx()->current_row = b.row - i;
cx()->current_col = 0;
std::vector<Char> cut;
parseLine(&cut, 0, true);
cutlist.push_back(std::move(cut));
}
if (op == EditOperation::Cut) {
lines->erase(lines->begin() + b.row - i);
}
}
cx()->current_row = a.row;
cx()->current_col = a.col;
int index = calcColumnToIndex(a.col);
std::vector<Char> vec2;
parseLine(&vec2, 0, true);
if (cutbuffer) {
std::vector<Char> cut;
cut.insert(cut.end(), vec2.begin() + index, vec2.end());
cutlist.push_back(std::move(cut));
}
if (op == EditOperation::Cut) {
vec2.resize(index);
vec2.insert(vec2.end(), vec1.begin(), vec1.end());
commitLine(vec2);
UpdateVisibility();
}
}
if (cutbuffer) {
size_t size = 0;
for (std::vector<Char> const &v : cutlist) {
size += v.size();
}
cutbuffer->reserve(size);
for (auto it = cutlist.rbegin(); it != cutlist.rend(); it++) {
std::vector<Char> const &v = *it;
cutbuffer->insert(cutbuffer->end(), v.begin(), v.end());
}
}
if (op == EditOperation::Cut) {
deselect();
setCursorPos(a.row, a.col);
invalidateArea(a.row - cx()->scroll_row_pos);
} else {
cx()->current_row = curr_row;
cx()->current_col = curr_col;
invalidateArea(curr_row - cx()->scroll_row_pos);
}
clearParsedLine();
UpdateVisibility();
}
void AbstractCharacterBasedApplication::edit_(EditOperation op)
{
std::vector<Char> cutbuf;
editSelected(op, &cutbuf);
if (cutbuf.empty()) return;
std::vector<uint32_t> u32buf;
u32buf.reserve(cutbuf.size());
for (Char const &c : cutbuf) {
u32buf.push_back(c.unicode);
}
std::vector<ushort> u16buf;
u16buf.reserve(1024);
utf32(u32buf.data(), u32buf.size()).to_utf16([&](uint16_t c){
u16buf.push_back(c);
return true;
});
if (!u16buf.empty()) {
QString s = QString::fromUtf16(&u16buf[0], u16buf.size());
qApp->clipboard()->setText(s);
}
}
bool AbstractCharacterBasedApplication::deleteIfSelected()
{
if (selection_anchor_0.enabled && selection_anchor_1.enabled) {
if (selection_anchor_0 != selection_anchor_1) {
editSelected(EditOperation::Cut, nullptr);
return true;
}
}
return false;
}
void AbstractCharacterBasedApplication::doDelete()
{
if (isReadOnly()) return;
if (isTerminalMode()) return;
if (deleteIfSelected()) {
return;
}
parseLine(nullptr, 0, false);
std::vector<Char> *vec = &m->prepared_current_line;
int index = m->parsed_col_index;
int c = -1;
if (index >= 0 && index < (int)vec->size()) {
c = (*vec)[index].unicode;
}
if (c == '\n' || c == '\r' || c == -1) {
if (index == 0) {
m->parsed_row_index--;
}
invalidateAreaBelowTheCurrentLine();
if (isSingleLineMode()) {
// nop
} else {
if (c != -1) {
vec->erase(vec->begin() + index);
if (c == '\r' && index < (int)vec->size() && (*vec)[index].unicode == '\n') {
vec->erase(vec->begin() + index);
}
}
if (vec->empty()) {
clearParsedLine();
if (cx()->current_row + 1 < (int)cx()->engine->document.lines.size()) {
cx()->engine->document.lines.erase(cx()->engine->document.lines.begin() + cx()->current_row);
}
} else {
commitLine(*vec);
setCursorColByIndex(*vec, index);
if (index == (int)vec->size()) {
int nextrow = cx()->current_row + 1;
int lines = documentLines();
if (nextrow < lines) {
Document::Line *ba1 = &cx()->engine->document.lines[cx()->current_row];
Document::Line const &ba2 = cx()->engine->document.lines[nextrow];
ba1->text.append(ba2.text);
cx()->engine->document.lines.erase(cx()->engine->document.lines.begin() + nextrow);
}
}
}
clearParsedLine();
updateVisibility(true, true, true);
}
} else {
vec->erase(vec->begin() + index);
commitLine(*vec);
setCursorColByIndex(*vec, index);
updateVisibility(true, true, true);
}
}
void AbstractCharacterBasedApplication::doBackspace()
{
if (isReadOnly()) return;
if (isTerminalMode()) return;
if (deleteIfSelected()) {
return ;
}
if (cx()->current_row > 0 || cx()->current_col > 0) {
setPaintingSuppressed(true);
moveCursorLeft();
doDelete();
setPaintingSuppressed(false);
updateVisibility(true, true, true);
}
}
bool AbstractCharacterBasedApplication::isDialogMode()
{
return m->dialog_mode;
}
void AbstractCharacterBasedApplication::setDialogOption(QString const &title, QString const &value, DialogHandler const &handler)
{
m->dialog_title = title;
m->dialog_value = value;
m->dialog_handler = handler;
}
void AbstractCharacterBasedApplication::setDialogMode(bool f)
{
if (f) {
if (!dialog_cx) {
int y = screenHeight() - 2;
dialog_cx = std::make_shared<TextEditorContext>();
dialog_cx->engine = std::make_shared<TextEditorEngine>();
dialog_cx->single_line = true;
dialog_cx->viewport_org_x = 0;
dialog_cx->viewport_org_y = y + 1;
dialog_cx->viewport_width = screenWidth();
dialog_cx->viewport_height = 1;
}
dialog_cx->engine->document.lines.push_back(Document::Line(m->dialog_value.toUtf8()));
editor_cx->viewport_height = screenHeight() - m->header_line - 2;
m->dialog_mode = true;
clearParsedLine();
moveCursorEnd();
} else {
dialog_cx.reset();
m->dialog_mode = false;
layoutEditor();
clearParsedLine();
updateVisibility(true, true, true);
}
}
void AbstractCharacterBasedApplication::execDialog(QString const &dialog_title, QString const &dialog_value, DialogHandler const &handler)
{
setDialogOption(dialog_title, dialog_value, handler);
setDialogMode(true);
}
void AbstractCharacterBasedApplication::closeDialog(bool result)
{
if (isDialogMode()) {
deselect();
QString line;
if (!dialog_cx->engine->document.lines.empty()) {
Document::Line const &l = dialog_cx->engine->document.lines.front();
line = QString::fromUtf8(l.text);
}
setDialogMode(false);
if (m->dialog_handler) {
m->dialog_handler(result, line);
}
return;
}
}
int AbstractCharacterBasedApplication::internalParseLine(QByteArray const &parsed_line, std::vector<Char> *vec, int increase_hint) const
{
vec->clear();
int index = -1;
int col = 0;
int len = parsed_line.size();
if (len > 0) {
vec->reserve(len + increase_hint);
char const *src = parsed_line.data();
utf8 u8(src, len);
while (1) {
int n = 0;
uint32_t c = u8.next();
if (c == 0) {
n = 1;
} else {
if (c == '\t') {
int z = nextTabStop(col);
n = z - col;
} else {
n = charWidth(c);
}
}
if (col <= cx()->current_col && col + n > cx()->current_col) {
index = (int)vec->size();
}
if (c == 0) break;
col += n;
vec->push_back(Char(c, 0));
}
}
return index;
}
int AbstractCharacterBasedApplication::calcColumnToIndex(int column)
{
int index = 0;
if (column > 0) {
fetchCurrentLine();
int col = 0;
int len = m->parsed_line.size();
if (len > 0) {
char const *src = m->parsed_line.data();
utf8 u8(src, len);
while (1) {
uint32_t c = u8.next();
int n = 0;
if (c == '\r' || c == '\n' || c == 0) {
break;
}
if (c == '\t') {
int z = nextTabStop(col);
n = z - col;
} else {
n = charWidth(c);
}
col += n;
index++;
if (col >= column) {
break;
}
}
}
}
return index;
}
void AbstractCharacterBasedApplication::invalidateArea(int top_y)
{
int y0 = cx()->viewport_org_y;
int y1 = cx()->viewport_height + y0;
top_y += y0;
if (y0 < top_y) y0 = top_y;
int n = m->line_flags.size();
if (y0 < 0) y0 = 0;
if (y1 > n) y1 = n;
for (int y = y0; y < y1; y++) {
m->line_flags[y] |= LineChanged;
}
}
void AbstractCharacterBasedApplication::invalidateAreaBelowTheCurrentLine()
{
int y;
y = m->parsed_row_index;
if (m->valid_line_index > y) {
m->valid_line_index = y;
}
y = m->parsed_row_index - cx()->scroll_row_pos;
invalidateArea(y);
}
int AbstractCharacterBasedApplication::scrollBottomLimit() const
{
return documentLines() - editorViewportHeight() / 2;
}
void AbstractCharacterBasedApplication::writeCR()
{
deleteIfSelected();
setCursorCol(0);
clearParsedLine();
updateVisibility(true, true, true);
}
void AbstractCharacterBasedApplication::moveCursorOut()
{
setCursorRow(-1);
}
void AbstractCharacterBasedApplication::moveCursorHome()
{
fetchCurrentLine();
QString line = m->parsed_line;
ushort const *ptr = line.utf16();
ushort const *end = ptr + line.size();
int x = 0;
while (1) {
int c = -1;
if (ptr < end) {
c = *ptr;
ptr++;
}
if (c == ' ') {
x++;
} else if (c == '\t') {
x = nextTabStop(x);
} else {
break;
}
}
if (x == cx()->current_col) {
x = 0;
}
setCursorCol(x);
clearParsedLine();
updateVisibility(true, true, true);
}
void AbstractCharacterBasedApplication::moveCursorEnd()
{
fetchCurrentLine();
QByteArray line = m->parsed_line;
int col = calcVisualWidth(Document::Line(line));
setCursorCol(col);
clearParsedLine();
updateVisibility(true, true, true);
}
void AbstractCharacterBasedApplication::scrollUp()
{
if (cx()->scroll_row_pos > 0) {
cx()->scroll_row_pos--;
invalidateArea();
clearParsedLine();
updateVisibility(false, false, true);
}
}
void AbstractCharacterBasedApplication::scrollDown()
{
int limit = scrollBottomLimit();
if (cx()->scroll_row_pos < limit) {
cx()->scroll_row_pos++;
invalidateArea();
clearParsedLine();
updateVisibility(false, false, true);
}
}
void AbstractCharacterBasedApplication::moveCursorUp()
{
if (isSingleLineMode()) {
// nop
} else if (cx()->current_row > 0) {
setCursorRow(cx()->current_row - 1);
clearParsedLine();
updateVisibility(true, false, true);
}
}
void AbstractCharacterBasedApplication::moveCursorDown()
{
if (isSingleLineMode()) {
// nop
} else if (cx()->current_row + 1 < (int)document()->lines.size()) {
setCursorRow(cx()->current_row + 1);
clearParsedLine();
updateVisibility(true, false, true);
}
}
void AbstractCharacterBasedApplication::scrollToTop()
{
if (isSingleLineMode()) return;
setCursorRow(0);
setCursorCol(0);
cx()->scroll_row_pos = 0;
invalidateArea();
clearParsedLine();
updateVisibility(true, false, true);
}
void AbstractCharacterBasedApplication::moveCursorLeft()
{
if (!isShiftModifierPressed() && selection_anchor_0.enabled && selection_anchor_1.enabled) { // 選択領域があったら
if (selection_anchor_0 != selection_anchor_1) {
SelectionAnchor a = std::min(selection_anchor_0, selection_anchor_1);
deselect();
setCursorRow(a.row);
setCursorCol(a.col);
updateVisibility(true, true, true);
return;
}
}
if (cx()->current_col == 0) {
if (isSingleLineMode()) {
// nop
} else {
if (cx()->current_row > 0) {
setCursorRow(cx()->current_row - 1);
clearParsedLine();
moveCursorEnd();
}
}
return;
}
int col = 0;
int newcol = 0;
int index;
for (index = 0; index < (int)m->prepared_current_line.size(); index++) {
uint32_t c = m->prepared_current_line[index].unicode;
if (c == '\r' || c == '\n') {
break;
}
newcol = col;
if (c == '\t') {
// col = nextTabStop(col);
col++;
} else {
// col += charWidth(c);
col++;
}
if (col >= cx()->current_col) {
break;
}
}
m->parsed_col_index = index;
setCursorCol(newcol);
updateVisibility(true, true, true);
}
void AbstractCharacterBasedApplication::moveCursorRight()
{
if (!isShiftModifierPressed() && selection_anchor_0.enabled && selection_anchor_1.enabled) { // 選択領域があったら
if (selection_anchor_0 != selection_anchor_1) {
SelectionAnchor a = std::max(selection_anchor_0, selection_anchor_1);
deselect();
setCursorRow(a.row);
setCursorCol(a.col);
updateVisibility(true, true, true);
return;
}
}
int col = 0;
int i = 0;
while (1) {
int c = -1;
if (i < (int)m->prepared_current_line.size()) {
c = m->prepared_current_line[i].unicode;
}
if (c == '\r' || c == '\n' || c == -1) {
if (!isSingleLineMode()) {
int nextrow = cx()->current_row + 1;
int lines = document()->lines.size();
if (nextrow < lines) {
setCursorPos(nextrow, 0);
clearParsedLine();
updateVisibility(true, true, true);
return;
}
}
break;
}
if (c == '\t') {
// col = nextTabStop(col);
col++;
} else {
// col += charWidth(c);
col++;
}
if (col > cx()->current_col) {
break;
}
i++;
}
if (col != cx()->current_col) {
setCursorCol(col);
clearParsedLine();
updateVisibility(true, true, true);
}
}
void AbstractCharacterBasedApplication::movePageUp()
{
if (!isSingleLineMode()) {
int step = editorViewportHeight();
setCursorRow(cx()->current_row - step);
cx()->scroll_row_pos -= step;
if (cx()->current_row < 0) {
cx()->current_row = 0;
}
if (cx()->scroll_row_pos < 0) {
cx()->scroll_row_pos = 0;
}
invalidateArea();
clearParsedLine();
updateVisibility(true, false, true);
}
}
void AbstractCharacterBasedApplication::movePageDown()
{
if (!isSingleLineMode()) {
int limit = documentLines();
if (limit > 0) {
limit--;
int step = editorViewportHeight();
setCursorRow(cx()->current_row + step);
cx()->scroll_row_pos += step;
if (cx()->current_row > limit) {
cx()->current_row = limit;
}
limit = scrollBottomLimit();
if (cx()->scroll_row_pos > limit) {
cx()->scroll_row_pos = limit;
}
} else {
setCursorRow(0);
cx()->scroll_row_pos = 0;
}
invalidateArea();
clearParsedLine();
updateVisibility(true, false, true);
}
}
void AbstractCharacterBasedApplication::scrollRight()
{
if (cx()->scroll_col_pos > 0) {
// cx()->scroll_col_pos--;
cx()->scroll_col_pos -= reference_char_width_;
} else {
cx()->scroll_col_pos = 0;
}
invalidateArea();
clearParsedLine();
updateVisibility(true, true, true);
}
void AbstractCharacterBasedApplication::scrollLeft()
{
// cx()->scroll_col_pos++;
cx()->scroll_col_pos += reference_char_width_;
invalidateArea();
clearParsedLine();
updateVisibility(true, true, true);
}
void AbstractCharacterBasedApplication::addNewLineToBottom()
{
int row = cx()->engine->document.lines.size();
if (cx()->current_row >= row) {
setCursorPos(row, 0);
cx()->engine->document.lines.push_back(Document::Line(QByteArray()));
}
}
void AbstractCharacterBasedApplication::appendNewLine(std::vector<Char> *vec)
{
if (isSingleLineMode()) return;
vec->emplace_back('\r', 0);
vec->emplace_back('\n', 0);
}
void AbstractCharacterBasedApplication::writeNewLine()
{
if (isReadOnly()) return;
if (isSingleLineMode()) return;
if (!isCurrentLineWritable()) return;
invalidateAreaBelowTheCurrentLine();
std::vector<Char> curr_line;
parseLine(&curr_line, 0, false);
int index = m->parsed_col_index;
if (index < 0) {
addNewLineToBottom();
index = 0;
}
std::vector<Char> next_line;
// split line
next_line.insert(next_line.end(), curr_line.begin() + index, curr_line.end());
// shrink current line
curr_line.resize(index);
// append new line code
appendNewLine(&curr_line);
// next line index
cx()->current_row = m->parsed_row_index + 1;
// commit current line
commitLine(curr_line);
// insert next line
m->parsed_row_index = cx()->current_row;
engine()->document.lines.insert(engine()->document.lines.begin() + m->parsed_row_index, Document::Line(QByteArray()));
// commit next line
commitLine(next_line);
setCursorColByIndex(next_line, 0);
clearParsedLine();
updateVisibility(true, true, true);
}
void AbstractCharacterBasedApplication::makeColumnPosList(std::vector<int> *out)
{
out->clear();
std::vector<Char> const *line = &m->prepared_current_line;
int x = 0;
while (1) {
size_t index = out->size();
out->push_back(x);
int n;
int c = -1;
if (index < line->size()) {
c = line->at(index).unicode;
}
if (c == '\r' || c == '\n' || c == -1) {
break;
}
if (c == '\t') {
int z = nextTabStop(x);
// n = z - x;
n = 1;
} else {
// n = charWidth(c);
n = 1;
}
x += n;
}
}
void AbstractCharacterBasedApplication::updateCursorPos(bool auto_scroll)
{
if (!cx()->engine) {
return;
}
parseLine(&m->prepared_current_line, 0, false);
int index = 0;
int char_span = 0;
int col = cx()->current_col_hint;
{
std::vector<int> pts;
makeColumnPosList(&pts);
if (pts.size() > 1) {
int newcol = pts.back();
int newindex = pts.size() - 1;
for (size_t i = 0; i + 1 < pts.size(); i++) {
int x = pts[i];
if (x <= col && col < pts[i + 1]) {
char_span = pts[i + 1] - pts[i];
newindex = i;
newcol = x;
break;
}
}
index = newindex;
col = newcol;
} else if (!pts.empty()) {
col = pts.back();
} else {
col = 0;
}
}
m->parsed_col_index = index;
cx()->current_col = col;
if (char_span < 1) {
char_span = 1;
}
cx()->current_char_span = char_span;
if (auto_scroll) {
int pos = decideColumnScrollPos();
if (cx()->scroll_col_pos != pos) {
cx()->scroll_col_pos = pos;
invalidateArea();
}
}
}
void AbstractCharacterBasedApplication::printInvertedBar(int x, int y, char const *text, int padchar)
{
int w = screenWidth();
int o = w * y;
for (int i = 0; i < w; i++) {
m->screen[o + i].c = 0;
}
AbstractCharacterBasedApplication::Option opt;
opt.char_attr = CharAttr::Invert;
print(x, y, text, opt);
for (int i = 0; i < w; i++) {
if (m->screen[o + i].c == 0) {
m->screen[o + i].c = padchar;
}
m->screen[o + i].a = opt.char_attr;
}
}
QString AbstractCharacterBasedApplication::statusLine() const
{
QString text = "[%1:%2]";
text = text.arg(cx()->current_row + 1).arg(cx()->current_col + 1);
return text;
}
int AbstractCharacterBasedApplication::printArea(TextEditorContext const *cx, const SelectionAnchor *sel_a, const SelectionAnchor *sel_b)
{
int end_of_line_y = -1;
if (cx) {
int height = cx->viewport_height;
QRect clip(cx->viewport_org_x, cx->viewport_org_y, cx->viewport_width, height);
int row = cx->scroll_row_pos;
for (int i = 0; i < height; i++) {
if (row < 0) continue;
int y = cx->viewport_org_y + i;
if (row < (int)cx->engine->document.lines.size()) {
if (i < height) {
int x = cx->viewport_org_x - cx->scroll_col_pos;
Document::Line const &line = cx->engine->document.lines[row];
int anchor_a = -1;
int anchor_b = -1;
if (sel_a && sel_a->enabled && sel_b && sel_b->enabled) {
SelectionAnchor a = *sel_a;
SelectionAnchor b = *sel_b;
if (a.row > b.row) {
std::swap(a, b);
} else if (a.row == b.row) {
if (a.col > b.col) {
std::swap(a, b);
}
}
if (row > a.row && row < b.row) {
anchor_a = 0;
} else {
if (row == a.row) {
anchor_a = a.col;
}
if (row == b.row) {
anchor_b = b.col;
}
}
}
QList<FormattedLine> lines = formatLine(line, cx->tab_span, anchor_a, anchor_b);
for (FormattedLine const &line : lines) {
AbstractCharacterBasedApplication::Option opt;
if (line.atts & FormattedLine::StyleID) {
opt.char_attr.color = QColor(line.atts & 0xff, (line.atts >> 8) & 0xff, (line.atts >> 16) & 0xff);
}
opt.clip = clip;
if (line.isSelected()) {
opt.char_attr.flags |= CharAttr::Selected;
}
x = print(x, y, line.text, opt);
}
int end_x = clip.x() + clip.width();
if (x < end_x) {
if (x < clip.left()) {
x = clip.left();
}
if (x < end_x) {
clearRect(x, y, end_x - x, 1);
}
}
}
} else {
if (end_of_line_y < 0) {
end_of_line_y = i;
}
clearRect(cx->viewport_org_x, y, cx->viewport_width, 1);
if (y >= 0 && y < (int)m->line_flags.size()) {
m->line_flags[y] |= LineChanged;
}
}
row++;
}
}
return end_of_line_y;
}
void AbstractCharacterBasedApplication::paintLineNumbers(std::function<void(int, QString, Document::Line const *line)> const &draw)
{
auto Line = [&](int index)->Document::Line &{
return editor_cx->engine->document.lines[index];
};
int rightpadding = 2;
int left_margin = editor_cx->viewport_org_x;
int num = 1;
size_t offset = 0;
for (int i = 0; i < editor_cx->viewport_height; i++) {
char tmp[100];
Q_ASSERT(left_margin < (int)sizeof(tmp));
memset(tmp, ' ', left_margin);
tmp[left_margin] = 0;
int row = editor_cx->scroll_row_pos + i;
Document::Line *line = nullptr;
if (row < (int)editor_cx->engine->document.lines.size()) {
if (m->valid_line_index < 0) {
m->valid_line_index = 0;
Document::Line *p = &Line(0);
if (p->type != Document::Line::Unknown) {
p->byte_offset = offset;
p->line_number = num;
offset += p->text.size();
num++;
}
}
line = &Line(row);
if (row >= m->valid_line_index) {
{
Document::Line const &line = Line(m->valid_line_index);
offset = line.byte_offset;
num = line.line_number;
}
while (m->valid_line_index <= row) {
Document::Line const &line = Line(m->valid_line_index);
if (line.type != Document::Line::Unknown) {
offset += line.text.size();
num++;
}
m->valid_line_index++;
if (m->valid_line_index < editor_cx->engine->document.lines.size()) {
Document::Line *p = &Line(m->valid_line_index);
p->byte_offset = offset;
p->line_number = num;
}
}
}
if (left_margin > 1) {
unsigned int linenum = -1;
if (row < m->valid_line_index) {
#if 1
linenum = line->line_number;
#else
linenum = line->byte_offset;
#endif
}
if (linenum != (unsigned int)-1 && line->type != Document::Line::Unknown) {
sprintf(tmp, "%*u ", left_margin - rightpadding, linenum);
}
}
}
int y = editor_cx->viewport_org_y + i;
draw(y, tmp, line);
}
}
bool AbstractCharacterBasedApplication::isAutoLayout() const
{
return m->auto_layout;
}
void AbstractCharacterBasedApplication::preparePaintScreen()
{
if (m->header_line > 0) {
char const *line = "Hello, world\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86";
printInvertedBar(0, 0, line, ' ');
}
if (m->show_line_number) {
Option opt_normal;
paintLineNumbers([&](int y, QString text, Document::Line const *line){
(void)line;
print(0, y, text + '|', opt_normal);
});
}
SelectionAnchor anchor_a;
SelectionAnchor anchor_b;
auto MakeSelectionAnchor = [&](){
if (selection_anchor_0.enabled != SelectionAnchor::No) {
anchor_a = selection_anchor_0;
#if 0
anchor_b.row = cx()->current_row;
anchor_b.col = cx()->current_col;
anchor_b.enabled = selection_anchor_0.enabled;
#else
anchor_b = selection_anchor_1;
#endif
}
};
if (isDialogMode()) {
printArea(editor_cx.get(), &anchor_a, &anchor_b);
std::string text = m->dialog_title.toStdString();
text = ' ' + text + ' ';
int y = screenHeight() - 2;
printInvertedBar(3, y, text.c_str(), '-');
MakeSelectionAnchor();
printArea(dialog_cx.get(), &anchor_a, &anchor_b);
} else {
MakeSelectionAnchor();
editor_cx->bottom_line_y = printArea(editor_cx.get(), &anchor_a, &anchor_b);
if (m->footer_line > 0) {
QString line = statusLine();
int y = screenHeight() - 1;
printInvertedBar(0, y, line.toStdString().c_str(), ' ');
}
}
}
void AbstractCharacterBasedApplication::onQuit()
{
if (!m->is_quit_enabled) return;
if (!isDialogMode()) {
m->state = State::Exit;
}
}
void AbstractCharacterBasedApplication::onOpenFile()
{
if (isReadOnly()) return;
if (!m->is_open_enabled) return;
if (!isDialogMode()) {
execDialog("Open File", recentlyUsedPath(), [&](bool ok, QString const &text){
if (ok) {
openFile(text);
}
});
}
}
void AbstractCharacterBasedApplication::onSaveFile()
{
if (!m->is_save_enabled) return;
if (!isDialogMode()) {
execDialog("Save File", recentlyUsedPath(), [&](bool ok, QString const &text){
if (ok) {
saveFile(text);
}
});
}
}
void AbstractCharacterBasedApplication::setNormalTextEditorMode(bool f)
{
m->is_quit_enabled = f;
m->is_open_enabled = f;
m->is_save_enabled = f;
setTerminalMode(!f);
}
SelectionAnchor AbstractCharacterBasedApplication::currentAnchor(SelectionAnchor::Enabled enabled)
{
SelectionAnchor a;
a.row = cx()->current_row;
a.col = cx()->current_col;
a.enabled = enabled;
return a;
}
void AbstractCharacterBasedApplication::deselect()
{
selection_anchor_0.enabled = SelectionAnchor::No;
selection_anchor_1.enabled = SelectionAnchor::No;
}
bool AbstractCharacterBasedApplication::isSelectionAnchorEnabled() const
{
return selection_anchor_0.enabled != SelectionAnchor::No;
}
void AbstractCharacterBasedApplication::setToggleSelectionAnchorEnabled(bool f)
{
m->is_toggle_selection_anchor_enabled = f;
}
void AbstractCharacterBasedApplication::setReadOnly(bool f)
{
m->is_read_only = f;
}
bool AbstractCharacterBasedApplication::isReadOnly() const
{
return m->is_read_only && !m->is_terminal_mode;
}
void AbstractCharacterBasedApplication::setSelectionAnchor(SelectionAnchor::Enabled enabled, bool update_anchor, bool auto_scroll)
{
if (update_anchor) {
selection_anchor_0 = currentAnchor(enabled);
} else {
selection_anchor_0.enabled = enabled;
}
clearParsedLine();
updateVisibility(false, false, auto_scroll);
}
void AbstractCharacterBasedApplication::toggleSelectionAnchor()
{
if (!m->is_toggle_selection_anchor_enabled) return;
setSelectionAnchor(isSelectionAnchorEnabled() ? SelectionAnchor::No : SelectionAnchor::EnabledHard, true, true);
selection_anchor_1 = selection_anchor_0;
}
void AbstractCharacterBasedApplication::editPaste()
{
if (isReadOnly()) return;
if (isTerminalMode()) return;
setPaintingSuppressed(true);
QString str = qApp->clipboard()->text();
utf16(str.utf16(), str.size()).to_utf32([&](uint32_t c){
write(c, false);
return true;
});
setPaintingSuppressed(false);
updateVisibility(true, true, true);
}
void AbstractCharacterBasedApplication::editCopy()
{
edit_(EditOperation::Copy);
}
void AbstractCharacterBasedApplication::editCut()
{
if (isReadOnly()) return;
if (isTerminalMode()) return;
edit_(EditOperation::Cut);
}
void AbstractCharacterBasedApplication::setWriteMode(WriteMode wm)
{
m->write_mode = wm;
}
bool AbstractCharacterBasedApplication::isInsertMode() const
{
return m->write_mode == WriteMode::Insert && !isTerminalMode();
}
bool AbstractCharacterBasedApplication::isOverwriteMode() const
{
return m->write_mode == WriteMode::Overwrite || isTerminalMode();
}
void AbstractCharacterBasedApplication::setTerminalMode(bool f)
{
m->is_terminal_mode = f;
if (isTerminalMode()) {
showHeader(false);
showFooter(false);
showLineNumber(false, 0);
setLineMargin(0);
setWriteMode(WriteMode::Overwrite);
setReadOnly(true);
}
layoutEditor();
}
bool AbstractCharacterBasedApplication::isTerminalMode() const
{
return m->is_terminal_mode;
}
bool AbstractCharacterBasedApplication::isBottom() const
{
if (cx()->current_row == m->parsed_row_index) {
if (m->parsed_col_index == (int)m->prepared_current_line.size()) {
return true;
}
}
return false;
}
void AbstractCharacterBasedApplication::moveToTop()
{
if (isSingleLineMode()) return;
deselect();
cx()->current_row = 0;
cx()->current_col = 0;
cx()->current_col_hint = 0;
cx()->scroll_row_pos = 0;
scrollToTop();
invalidateArea();
clearParsedLine();
updateVisibility(true, false, true);
}
void AbstractCharacterBasedApplication::logicalMoveToBottom()
{
deselect();
cx()->current_row = documentLines();
cx()->current_col = 0;
if (cx()->current_row > 0) {
cx()->current_row = cx()->current_row - 1;
clearParsedLine();
fetchCurrentLine();
int col = calcVisualWidth(Document::Line(m->parsed_line));
cx()->current_col = col;
cx()->current_col_hint = col;
}
cx()->scroll_row_pos = scrollBottomLimit();
}
void AbstractCharacterBasedApplication::moveToBottom()
{
if (isSingleLineMode()) return;
logicalMoveToBottom();
invalidateArea();
clearParsedLine();
updateVisibility(true, false, true);
}
int AbstractCharacterBasedApplication::findSyntax(QList<Document::CharAttr_> const *list, size_t offset)
{
int lo = 0;
int hi = list->size();
while (lo + 1 < hi) {
int mid = (lo + hi) / 2;
Document::CharAttr_ const *a = &list->at(mid);
if (offset == a->offset) {
return mid;
}
if (offset < a->offset) {
hi = mid;
} else {
lo = mid;
}
}
return lo;
}
void AbstractCharacterBasedApplication::insertSyntax(QList<Document::CharAttr_> *list, size_t offset, Document::CharAttr_ const &a)
{
int i = findSyntax(list, offset);
int n = list->size();
if (i < n) {
if (list->at(i).offset == offset) {
(*list)[i] = a;
} else {
if (i < n) {
if (a.offset < list->at(i).offset) {
if (list->at(i).color == a.color) {
(*list)[i].offset = offset;
return;
}
}
}
if (list->at(i).color == a.color) {
return;
}
if (list->at(i).offset < a.offset) {
i++;
}
list->insert(list->begin() + i, a);
while (++i < n) {
(*list)[i].offset++;
}
}
} else {
list->push_back(a);
}
}
void AbstractCharacterBasedApplication::internalWrite(const ushort *begin, const ushort *end)
{
deleteIfSelected();
clearShiftModifier();
if (cx()->engine->document.lines.isEmpty()) {
Document::Line line;
line.type = Document::Line::Normal;
line.line_number = 1;
cx()->engine->document.lines.push_back(line);
}
if (!isCurrentLineWritable()) return;
int len = end - begin;
parseLine(nullptr, len, false);
int index = m->parsed_col_index;
if (index < 0) {
addNewLineToBottom();
index = 0;
}
std::vector<Char> *vec = &m->prepared_current_line;
- auto WriteChar = [&](ushort c){
+ auto WriteChar = [&](uint32_t c){
if (isInsertMode()) {
vec->insert(vec->begin() + index, Char(c, 0));
} else if (isOverwriteMode()) {
if (index < (int)vec->size()) {
uint32_t d = vec->at(index).unicode;
if (d == '\n' || d == '\r') {
vec->insert(vec->begin() + index, Char(c, 0));
} else {
vec->at(index) = Char(c, 0);
}
} else {
vec->emplace_back(c, 0);
}
}
Document::CharAttr_ a;
a.offset = index;
a.color = m->escape_sequence.fg_color_code();
insertSyntax(&m->syntax_table, index, a);
};
ushort const *ptr = begin;
while (ptr < end) {
ushort c = *ptr;
ptr++;
if (c >= 0xd800 && c < 0xdc00) {
if (ptr < end) {
ushort d = *ptr;
if (d >= 0xdc00 && d < 0xe000) {
ptr++;
int u = 0x10000 + (c - 0xd800) * 0x400 + (d - 0xdc00);
WriteChar(u);
index++;
}
}
} else {
WriteChar(c);
index++;
}
}
m->parsed_col_index = index;
commitLine(*vec);
setCursorColByIndex(*vec, index);
updateVisibility(true, true, true);
}
void AbstractCharacterBasedApplication::pressLetterWithControl(int c)
{
if (c < 0 || c > 0x7f) {
return;
}
if (c < 0x40) {
c += 0x40;
}
c = toupper(c);
switch (c) {
case 'A':
toggleSelectionAnchor();
break;
case 'Q':
onQuit();
break;
case 'O':
onOpenFile();
break;
case 'S':
onSaveFile();
break;
case 'X':
editCut();
break;
case 'C':
editCopy();
break;
case 'V':
editPaste();
break;
}
}
void AbstractCharacterBasedApplication::write(uint32_t c, bool by_keyboard)
{
if (isTerminalMode()) {
if (c == '\r') {
setCursorCol(0);
clearParsedLine();
updateVisibility(true, true, true);
return;
}
if (m->cursor_moved_by_mouse) {
moveToBottom();
}
if (c == 0x1b || m->escape_sequence.isActive()) {
m->escape_sequence.write(c);
return;
}
}
bool ok = !(isTerminalMode() && by_keyboard);
if (c < 0x20) {
if (c == 0x08) {
if (ok) {
doBackspace();
}
} else if (c == 0x09) {
if (ok) {
ushort u = c;
internalWrite(&u, &u + 1);
}
} else if (c == 0x0a) {
if (ok) {
pressEnter();
}
} else if (c == 0x0d) {
if (ok) {
writeCR();
}
} else if (c == 0x1b) {
pressEscape();
} else if (c >= 1 && c <= 26) {
pressLetterWithControl(c);
}
} else if (c == 0x7f) {
if (ok) {
doDelete();
}
} else if (c < 0x10000) {
if (ok) {
ushort u = c;
internalWrite(&u, &u + 1);
}
} else if (c >= 0x10000 && c <= 0x10ffff) {
if (ok) {
ushort t[2];
t[0] = (c - 0x10000) / 0x400 + 0xd800;
t[1] = (c - 0x10000) % 0x400 + 0xdc00;
internalWrite(t, t + 2);
}
} else {
switch (c) {
case EscapeCode::Up:
if (ok) moveCursorUp();
break;
case EscapeCode::Down:
if (ok) moveCursorDown();
break;
case EscapeCode::Right:
if (ok) moveCursorRight();
break;
case EscapeCode::Left:
if (ok) moveCursorLeft();
break;
case EscapeCode::Home:
if (ok) moveCursorHome();
break;
case EscapeCode::End:
if (ok) moveCursorEnd();
break;
case EscapeCode::PageUp:
if (ok) movePageUp();
break;
case EscapeCode::PageDown:
if (ok) movePageDown();
break;
case EscapeCode::Insert:
clearShiftModifier();
break;
case EscapeCode::Delete:
clearShiftModifier();
if (ok) doDelete();
break;
}
}
}
void AbstractCharacterBasedApplication::write(char const *ptr, int len, bool by_keyboard)
{
if (isReadOnly()) return;
char const *begin = ptr;
char const *end = begin + (len < 0 ? strlen(ptr) : len);
char const *left = begin;
char const *right = begin;
while (1) {
int c = -1;
if (right < end) {
c = *right & 0xff;
}
if (c == '\n' || c == '\r' || c < 0) {
utf8 src(left, right);
while (1) {
int d = src.next();
if (d == 0) break;
write(d, by_keyboard);
}
if (c < 0) break;
right++;
if (c == '\r') {
c = isInsertMode() ? '\n' : '\r';
if (right < end && *right == '\n') {
c = '\n';
right++;
}
write(c, by_keyboard);
} else if (c == '\n') {
write('\n', by_keyboard);
}
left = right;
} else {
right++;
}
}
}
void AbstractCharacterBasedApplication::write(std::string const &text)
{
if (!text.empty()) {
write(text.c_str(), text.size(), false);
}
}
void AbstractCharacterBasedApplication::write_(char const *ptr, bool by_keyboard)
{
write(ptr, -1, by_keyboard);
}
void AbstractCharacterBasedApplication::write_(QString const &text, bool by_keyboard)
{
if (isReadOnly()) return;
if (text.size() == 1) {
ushort c = text.at(0).unicode();
write(c, by_keyboard);
return;
}
int len = text.size();
if (len > 0) {
ushort const *begin = text.utf16();
ushort const *end = begin + len;
ushort const *left = begin;
ushort const *right = begin;
while (1) {
int c = -1;
if (right < end) {
c = *right;
}
if (c < 0x20) {
if (left < right) {
internalWrite(left, right);
}
if (c == -1) break;
right++;
if (c == '\n' || c == '\r') {
if (c == '\r') {
if (right < end && *right == '\n') {
right++;
}
}
writeNewLine();
} else {
write(c, by_keyboard);
}
left = right;
} else {
right++;
}
}
}
}
void AbstractCharacterBasedApplication::write(QKeyEvent *e)
{
setModifierKeys(e->modifiers());
int c = e->key();
if (c == Qt::Key_Backspace) {
write(0x08, true);
} else if (c == Qt::Key_Delete) {
write(0x7f, true);
} else if (c == Qt::Key_Up) {
if (isControlModifierPressed()) {
scrollUp();
} else {
write(EscapeCode::Up, true);
}
} else if (c == Qt::Key_Down) {
if (isControlModifierPressed()) {
scrollDown();
} else {
write(EscapeCode::Down, true);
}
} else if (c == Qt::Key_Left) {
write(EscapeCode::Left, true);
} else if (c == Qt::Key_Right) {
write(EscapeCode::Right, true);
} else if (c == Qt::Key_PageUp) {
write(EscapeCode::PageUp, true);
} else if (c == Qt::Key_PageDown) {
write(EscapeCode::PageDown, true);
} else if (c == Qt::Key_Home) {
if (isControlModifierPressed()) {
moveToTop();
} else {
write(EscapeCode::Home, true);
}
} else if (c == Qt::Key_End) {
if (isControlModifierPressed()) {
moveToBottom();
} else {
write(EscapeCode::End, true);
}
} else if (c == Qt::Key_Return || c == Qt::Key_Enter) {
write('\n', true);
} else if (c == Qt::Key_Escape) {
write(0x1b, true);
} else if (isControlModifierPressed()) {
if (QChar(c).isLetter()) {
c = QChar(c).toUpper().unicode();
if (c >= 0x40 && c < 0x60) {
write(c - 0x40, true);
}
}
} else {
QString text = e->text();
write_(text, true);
}
}
void Document::retrieveLastText(std::vector<char> *out, int maxlen) const
{
int remain = maxlen;
int i = lines.size();
while (i > 0 && remain > 0) {
i--;
QByteArray const &data = lines[i].text;
int n = data.size();
if (n > remain) {
n = remain;
}
char const *p = data.data() + data.size() - n;
out->insert(out->begin(), p, p + n);
remain -= n;
}
}
//AbstractCharacterBasedApplication::Char::operator unsigned int() const
//{
// qDebug();
// return 'A';
//}
diff --git a/src/texteditor/TextEditorWidget.cpp b/src/texteditor/TextEditorWidget.cpp
index aede351..11398fb 100644
--- a/src/texteditor/TextEditorWidget.cpp
+++ b/src/texteditor/TextEditorWidget.cpp
@@ -1,909 +1,885 @@
#include "TextEditorWidget.h"
#include "TextEditorWidget.h"
#include <QDebug>
#include <QFile>
#include <QFontDatabase>
#include <QKeyEvent>
#include <QPainter>
#include <QScrollBar>
#include <QApplication>
#include <functional>
#include <QMenu>
#include <QTextCodec>
#include <QTimer>
#include "common/misc.h"
#include "InputMethodPopup.h"
#include "unicode.h"
class CharacterSize {
private:
QFont font_;
mutable int ascii_[128];
mutable int height_ = 0;
mutable std::map<unsigned int, int> map_;
public:
void reset(QFont const &font)
{
font_ = font;
height_ = 0;
map_.clear();
for (int i = 0; i < 128; i++) {
ascii_[i] = 0;
}
}
int width(unsigned int c) const
{
if (height_ == 0) {
QPixmap pm(1, 1);
QPainter pr(&pm);
pr.setFont(font_);
QFontMetrics fm = pr.fontMetrics();
height_ = fm.ascent() + fm.descent();
for (int i = 0; i < 0x20; i++) {
ascii_[i] = 0;
}
for (int i = 0x20; i < 0x80; i++) {
char tmp[2];
tmp[0] = i;
tmp[1] = 0;
ascii_[i] = fm.size(0, tmp).width();
}
}
if (c < 0x80) {
return ascii_[c];
}
auto it = map_.find(c);
if (it != map_.end()) {
return it->second;
} else {
QPixmap pm(1, 1);
QPainter pr(&pm);
pr.setFont(font_);
QFontMetrics fm = pr.fontMetrics();
QString s = QChar(c);
int w = fm.size(0, s).width();
map_[c] = w;
return w;
}
}
int height() const
{
if (height_ == 0) {
width(' ');
}
return height_;
}
};
struct TextEditorWidget::Private {
PreEditText preedit;
QFont text_font;
InputMethodPopup *ime_popup = nullptr;
int top_margin = 0;
int bottom_margin = 0;
CharacterSize character_size;
-// int latin1_width_ = 0;
-// int line_height_ = 0;
int ascent = 0;
int descent = 0;
QString status_line;
QScrollBar *scroll_bar_v = nullptr;
QScrollBar *scroll_bar_h = nullptr;
TextEditorThemePtr theme;
int wheel_delta = 0;
bool is_focus_frame_visible = false;
unsigned int idle_count = 0;
std::function<void(void)> custom_context_menu_requested;
};
TextEditorWidget::TextEditorWidget(QWidget *parent)
: QWidget(parent)
, m(new Private)
{
#ifdef Q_OS_WIN
setTextFont(QFont("MS Gothic", 10));
// setTextFont(QFont("MS PGothic", 20));
#endif
#ifdef Q_OS_LINUX
setTextFont(QFont("Monospace", 9));
#endif
#ifdef Q_OS_MACX
setTextFont(QFontDatabase().font("Osaka", "Regular-Mono", 14));
#endif
#ifdef Q_OS_HAIKU
setTextFont(QFont("Noto Mono", 9));
#endif
m->top_margin = 0;
m->bottom_margin = 1;
QPixmap pm(1, 1);
QPainter pr(&pm);
pr.setFont(textFont());
QFontMetrics fm = pr.fontMetrics();
m->ascent = fm.ascent();
m->descent = fm.descent();
-// reference_char_width_ = fm.width('A');
initEditor();
setFont(textFont());
setAttribute(Qt::WA_InputMethodEnabled);
#ifdef Q_OS_WIN
// m->ime_popup = new InputMethodPopup();
// m->ime_popup->setFont(font());
// m->ime_popup->setPreEditText(PreEditText());
#endif
setContextMenuPolicy(Qt::DefaultContextMenu);
setRenderingMode(DecoratedMode);
updateCursorRect(true);
startTimer(100);
}
TextEditorWidget::~TextEditorWidget()
{
delete m;
}
void TextEditorWidget::setTheme(TextEditorThemePtr const &theme)
{
m->theme = theme;
}
TextEditorTheme const *TextEditorWidget::theme() const
{
if (!m->theme) {
const_cast<TextEditorWidget *>(this)->setTheme(TextEditorTheme::Light());
}
return m->theme.get();
}
void TextEditorWidget::setTextFont(QFont const &font)
{
m->text_font = font;
m->character_size.reset(m->text_font);
}
void TextEditorWidget::setRenderingMode(RenderingMode mode)
{
rendering_mode = mode;
if (rendering_mode == DecoratedMode) {
showLineNumber(false);
} else {
showLineNumber(true);
}
update();
}
void AbstractCharacterBasedApplication::loadExampleFile()
{
#ifdef Q_OS_WIN
QString path = "C:/develop/ore/example.txt";
#elif defined(Q_OS_MAC)
QString path = "/Users/soramimi/develop/ore/example.txt";
#else
QString path = "/home/soramimi/develop/ore/example.txt";
#endif
openFile(path);
}
bool TextEditorWidget::event(QEvent *event)
{
if (event->type() == QEvent::Polish) {
clearParsedLine();
updateVisibility(true, true, true);
}
return QWidget::event(event);
}
int TextEditorWidget::charWidth2(unsigned int c) const
{
return m->character_size.width(c);
}
int TextEditorWidget::lineHeight() const
{
return m->character_size.height() + m->top_margin + m->bottom_margin;
}
int TextEditorWidget::defaultCharWidth() const
{
return m->character_size.width('8');
// return charWidth2('W');
}
class chars : public abstract_unicode_reader {
private:
TextEditorWidget::Char const *ptr;
size_t len;
size_t pos = 0;
public:
chars(TextEditorWidget::Char const *ptr, size_t len)
: ptr(ptr)
, len(len)
{
}
virtual uint32_t next() override
{
if (pos < len) {
return ptr[pos++].unicode;
}
return 0;
}
};
-int TextEditorWidget::parseLine3(int row, std::vector<Char> *vec, int *last_pos) const
+int TextEditorWidget::parseLine3(int row, int col, std::vector<Char> *vec) const
{
- int index = parseLine2(row, vec);
-#if 0
- {
- int pos = 0;
- for (int i = 0; i < (int)vec->size(); i++) {
- vec->at(i).pos = pos;
- pos += m->character_size.width(vec->at(i).unicode);
- }
- }
-#else
+ parseLine2(row, vec);
QPixmap pm(1, 1);
QPainter pr(&pm);
pr.setFont(m->text_font);
int pos = 0;
- size_t i = 0;
QString s;
- chars in(vec->data(), vec->size());
- in.to_utf16([&](uint16_t c){
+ for (size_t i = 0; i < vec->size(); i++) {
vec->at(i).pos = pos;
- s += QChar(c);
+ ushort tmp[3];
+ uint32_t u = vec->at(i).unicode;
+ if (u >= 0x10000 && u < 0x110000) {
+ // サロゲートペア
+ uint16_t h = (u - 0x10000) / 0x400 + 0xd800;
+ uint16_t l = (u - 0x10000) % 0x400 + 0xDC00;
+ tmp[0] = h;
+ tmp[1] = l;
+ tmp[2] = 0;
+ } else {
+ tmp[0] = u;
+ tmp[1] = 0;
+ }
+ s += QString::fromUtf16(tmp);
pos = pr.fontMetrics().size(0, s).width();
- i++;
- return true;
- });
- if (last_pos) *last_pos = pos;
-
-#endif
- return index;
+ }
+ if (vec->empty()) {
+ pos = 0;
+ } else if (col >= 0 && col < (int)vec->size()) {
+ pos = vec->at(col).pos;
+ } else {
+ pos = pos;
+ }
+ return pos;
}
QPoint TextEditorWidget::mapFromPixel(QPoint const &pt)
{
- if (1) {
- int col = cx()->current_col;
- qDebug() << col;
-// int x = pt.x() / defaultCharWidth();
- int y = pt.y() / lineHeight();
- int row = y + cx()->scroll_row_pos - cx()->viewport_org_y;
- int w = defaultCharWidth();
- int x = pt.x() + (cx()->scroll_col_pos - cx()->viewport_org_x) * w;
-// int x = pt.x() + cx()->scroll_col_pos - cx()->viewport_org_x;
- std::vector<Char> vec;
- parseLine3(row, &vec, nullptr);
-// {
-// int pos = 0;
-// for (int i = 0; i < (int)vec.size(); i++) {
-// vec[i].pos = pos;
-// pos += m->character_size.width(vec[i].unicode);
-// }
-// }
- for (int col = 0; col + 1 < (int)vec.size(); col++) {
- if (x < (int)vec[col + 1].pos) {
- return QPoint(col, row);
- }
+ const int col = cx()->current_col;
+ const int y = pt.y() / lineHeight();
+ const int row = y + cx()->scroll_row_pos - cx()->viewport_org_y;
+ const int w = defaultCharWidth();
+ const int x = pt.x() + (cx()->scroll_col_pos - cx()->viewport_org_x) * w;
+ std::vector<Char> vec;
+ parseLine3(row, col, &vec);
+ for (int col = 0; col + 1 < (int)vec.size(); col++) {
+ if (x < (int)vec[col + 1].pos) {
+ return QPoint(col, row);
}
- return QPoint(vec.size(), row);
}
-
- int x = pt.x() / defaultCharWidth();
- int y = pt.y() / lineHeight();
- return QPoint(x, y);
+ return QPoint(vec.size(), row);
}
QPoint TextEditorWidget::mapToPixel(QPoint const &pt)
{
int x = pt.x() * defaultCharWidth();
int y = pt.y() * lineHeight();
return QPoint(x, y);
}
void TextEditorWidget::bindScrollBar(QScrollBar *vsb, QScrollBar *hsb)
{
m->scroll_bar_v = vsb;
m->scroll_bar_h = hsb;
}
void TextEditorWidget::setupForLogWidget(QScrollBar *vsb, QScrollBar *hsb, TextEditorThemePtr const &theme)
{
bindScrollBar(vsb, hsb);
setTheme(theme);
setAutoLayout(true);
setTerminalMode(true);
layoutEditor();
}
QRect TextEditorWidget::updateCursorRect(bool auto_scroll)
{
updateCursorPos(auto_scroll);
int x = cx()->viewport_org_x + cursorX();
int y = cx()->viewport_org_y + cursorY();
x *= defaultCharWidth();
y *= lineHeight();
QPoint pt = QPoint(x, y);
int w = cx()->current_char_span * defaultCharWidth();
int h = lineHeight();
cx()->cursor_rect = QRect(pt.x(), pt.y(), w, h);
QApplication::inputMethod()->update(Qt::ImCursorRectangle);
return cx()->cursor_rect;
}
void TextEditorWidget::internalUpdateScrollBar()
{
QScrollBar *sb;
sb = m->scroll_bar_v;
if (sb) {
sb->blockSignals(true);
sb->setRange(0, document()->lines.size() - cx()->viewport_height / 2);
sb->setPageStep(editorViewportHeight());
sb->setValue(cx()->scroll_row_pos);
sb->blockSignals(false);
}
sb = m->scroll_bar_h;
if (sb) {
int w = editorViewportWidth();
sb->blockSignals(true);
sb->setRange(0, (w + 100) * reference_char_width_);
sb->setPageStep(w * reference_char_width_);
sb->setValue(cx()->scroll_col_pos);
sb->blockSignals(false);
}
emit updateScrollBar();
}
void TextEditorWidget::internalUpdateVisibility(bool ensure_current_line_visible, bool change_col, bool auto_scroll)
{
if (ensure_current_line_visible) {
ensureCurrentLineVisible();
}
updateCursorRect(auto_scroll);
if (change_col) {
cx()->current_col_hint = cx()->current_col;
}
if (isPaintingSuppressed()) {
return;
}
internalUpdateScrollBar();
update();
}
void TextEditorWidget::updateVisibility(bool ensure_current_line_visible, bool change_col, bool auto_scroll)
{
internalUpdateVisibility(ensure_current_line_visible, change_col, auto_scroll);
emit moved(cx()->current_row, cx()->current_col, cx()->scroll_row_pos, cx()->scroll_col_pos);
}
void TextEditorWidget::move(int cur_row, int cur_col, int scr_row, int scr_col, bool auto_scroll)
{
if ((cur_row >= 0 && cx()->current_row != cur_row) || (cur_col >= 0 && cx()->current_col != cur_col) || cx()->scroll_row_pos != scr_row || cx()->scroll_col_pos != scr_col) {
if (cur_row >= 0) cx()->current_row = cur_row;
if (cur_col >= 0) cx()->current_col = cur_col;
if (scr_row >= 0) cx()->scroll_row_pos = scr_row;
if (scr_col >= 0) cx()->scroll_col_pos = scr_col;
internalUpdateVisibility(false, true, auto_scroll);
}
}
//void TextEditorWidget::setPreEditText(const PreEditText &preedit)
//{
// m->preedit = preedit;
// update();
//}
QFont TextEditorWidget::textFont() const
{
return m->text_font;
}
void TextEditorWidget::drawText(QPainter *painter, int px, int py, QString const &str)
{
painter->drawText(px, py + lineHeight() - m->bottom_margin - m->descent, str);
}
QColor TextEditorWidget::defaultForegroundColor()
{
if (renderingMode() == DecoratedMode) {
return theme()->fgDefault();
}
return Qt::white;
}
QColor TextEditorWidget::defaultBackgroundColor()
{
if (renderingMode() == DecoratedMode) {
return theme()->bgDefault();
}
return Qt::black;
}
QColor TextEditorWidget::colorForIndex(CharAttr const &attr, bool foreground)
{
if (foreground && attr.color.isValid()) {
return attr.color;
}
switch (attr.index) {
case CharAttr::Invert:
return foreground ? defaultBackgroundColor() : defaultForegroundColor();
}
return foreground ? defaultForegroundColor() : Qt::transparent;//defaultBackgroundColor();
}
void TextEditorWidget::paintScreen(QPainter *painter)
{
int w = screenWidth();
int h = screenHeight();
for (int y = 0; y < h; y++) {
int x = 0;
int x2 = 0;
int x3 = 0;
while (x < w) {
std::vector<uint16_t> text;
text.reserve(w);
int o = y * w;
CharAttr charattr;
Character const *line = &char_screen()->at(o);
int n = 0;
while (x + n < w) {
uint32_t c = line[x + n].c;
uint32_t d = 0;
if (c == 0 || c == 0xffff) break;
if ((c & 0xfc00) == 0xdc00) {
// surrogate 2nd
break;
}
uint32_t unicode = c;
if ((c & 0xfc00) == 0xd800) {
// surrogate 1st
if (x + n + 1 < w) {
uint16_t t = line[x + n + 1].c;
if ((t & 0xfc00) == 0xdc00) {
d = t;
unicode = (((c & 0x03c0) + 0x0040) << 10) | ((c & 0x003f) << 10) | (d & 0x03ff);
} else {
break;
}
} else {
break;
}
}
int cw = charWidth(unicode);
if (cw < 1) break;
if (n == 0) {
charattr = line[x].a;
} else if (charattr != line[x + n].a) {
break;
}
if (d == 0) {
text.push_back(c);
} else { // surrogate pair
text.push_back(c);
text.push_back(d);
}
x3 += charWidth2(unicode);
n += cw;
}
if (n == 0) {
n = 1;
x3 += defaultCharWidth();
} else if (!text.empty()) {
QString str = QString::fromUtf16(&text[0], text.size());
int px = x * defaultCharWidth();
int py = y * lineHeight();
px = x2;
painter->setFont(textFont());
QFontMetrics fm = painter->fontMetrics();
int w = fm.width(str);
int h = lineHeight();
QColor fgcolor = colorForIndex(charattr, true);
QColor bgcolor = colorForIndex(charattr, false);
painter->fillRect(px, py, w, h, bgcolor);
painter->setPen(fgcolor);
drawText(painter, px, py, str);
}
x += n;
x2 = x3;
}
}
}
-void TextEditorWidget::drawCursor(QPainter *pr)
-{
- int col = cx()->viewport_org_x + cursorX();
- int row = cx()->viewport_org_y + cursorY();
- if (col < cx()->viewport_org_x || col >= cx()->viewport_org_x + cx()->viewport_width) return;
- if (row < cx()->viewport_org_y || row >= cx()->viewport_org_y + cx()->viewport_height) return;
- int h = lineHeight();
- int x = col * defaultCharWidth();
- int y = row * h;
- {
- col = cursorX();
- qDebug() << col;
- std::vector<Char> vec;
- int r = cursorY() + cx()->scroll_row_pos - cx()->viewport_org_y;
-
- parseLine3(r, &vec, &x);
- {
- QString s;
- for (auto t : vec) {
- s += t.unicode;
- }
- }
- if (vec.empty()) {
- x = 0;
- } else if (col >= 0 && col < (int)vec.size()) {
- x = vec[col].pos;
- }
- }
- x += cx()->viewport_org_x * defaultCharWidth();
- pr->fillRect(x -1, y, 2, h, theme()->fgCursor());
- pr->fillRect(x - 2, y, 4, 2, theme()->fgCursor());
- pr->fillRect(x - 2, y + h - 2, 4, 2, theme()->fgCursor());
-}
-
void TextEditorWidget::drawFocusFrame(QPainter *pr)
{
misc::drawFrame(pr, 0, 0, width(), height(), QColor(0, 128, 255, 128));
misc::drawFrame(pr, 1, 1, width() - 2, height() - 2, QColor(0, 128, 255, 64));
}
+#define SCROLL_BY_PIXEL 0
+int TextEditorWidget::xScrollPosInPixel()
+{
+#if SCROLL_BY_PIXEL
+ return editor_cx->scroll_col_pos;// * defaultCharWidth();
+#else
+ return editor_cx->scroll_col_pos * defaultCharWidth();
+#endif
+}
+
+void TextEditorWidget::drawCursor(QPainter *pr)
+{
+ std::vector<Char> vec;
+ int row = cursorY() + cx()->scroll_row_pos - cx()->viewport_org_y;
+ const int col = cx()->current_col;
+ int x = parseLine3(row, col, &vec);
+ x += cx()->viewport_org_x * defaultCharWidth();
+ x -= xScrollPosInPixel();
+ const int lineheight = lineHeight();
+ const int y = (cx()->viewport_org_y + cursorY()) * lineheight;
+ pr->fillRect(x -1, y, 2, lineheight, theme()->fgCursor());
+ pr->fillRect(x - 2, y, 4, 2, theme()->fgCursor());
+ pr->fillRect(x - 2, y + lineheight - 2, 4, 2, theme()->fgCursor());
+}
+
void TextEditorWidget::paintEvent(QPaintEvent *)
{
bool has_focus = hasFocus();
preparePaintScreen();
QPainter pr(this);
pr.fillRect(0, 0, width(), height(), defaultBackgroundColor());
// diff mode
if (renderingMode() == DecoratedMode) {
auto FillRowBackground = [&](int row, QColor const &color){
int y = editor_cx->viewport_org_y + row - editor_cx->scroll_row_pos;
y *= lineHeight();
pr.fillRect(0, y, width(), lineHeight(), color);
};
Document const &doc = editor_cx->engine->document;
for (int i = 0; i < editor_cx->viewport_height; i++) {
int row = i + editor_cx->scroll_row_pos;
if (row < doc.lines.size()) {
if (doc.lines[row].type == Document::Line::Unknown) {
FillRowBackground(row, theme()->bgDiffUnknown());
} else if (doc.lines[row].type == Document::Line::Add) {
FillRowBackground(row, theme()->bgDiffAdd());
} else if (doc.lines[row].type == Document::Line::Del) {
FillRowBackground(row, theme()->bgDiffDel());
}
}
}
}
auto visualY = [&](TextEditorContext const *context){
return context->viewport_org_y + editor_cx->current_row - editor_cx->scroll_row_pos;
};
- // draw cursor
+ // カーソル描画
if (has_focus) {
if (isCursorVisible()) {
- // current line
+ // 現在行の下線
if (renderingMode() == DecoratedMode) {
int x = cx()->viewport_org_x * defaultCharWidth();
int y = visualY(cx()) * lineHeight();
pr.fillRect(x, y, width() - x, lineHeight(), theme()->bgCurrentLine());
pr.fillRect(x, y + lineHeight() - 1, width() - x, 1, theme()->fgCursor());
}
+ // カーソル
drawCursor(&pr);
}
}
int linenum_width = editor_cx->viewport_org_x * defaultCharWidth();
+ // テキスト描画
if (renderingMode() == DecoratedMode) {
-// pr.fillRect(linenum_width, 0, width() - linenum_width, height(), Qt::blue);
int view_row = 0;
int line_row = editor_cx->scroll_row_pos;
int lh = lineHeight();
pr.save();
pr.setClipRect(linenum_width, 0, width() - linenum_width, height());
- auto ScrollXinPixel = [&](){
- return editor_cx->scroll_col_pos * defaultCharWidth();
- };
while (isValidRowIndex(line_row)) {
- int x = linenum_width - ScrollXinPixel();
+ int x = linenum_width - xScrollPosInPixel();
int y = view_row * lh;
if (y >= height()) break;
QList<FormattedLine> fline = formatLine2(line_row);
for (FormattedLine const &fl : fline) {
- pr.setPen(QPen(Qt::white));
+ pr.setPen(defaultForegroundColor());
pr.drawText(QRect(x, y, width() - linenum_width, lh), fl.text);
}
view_row++;
line_row++;
}
pr.restore();
} else {
paintScreen(&pr);
}
+ // 行番号描画
if (renderingMode() == DecoratedMode) {
auto FillLineNumberBG = [&](int y, int h, QColor color){
pr.fillRect(0, y, linenum_width - 2, h, color);
};
int bottom = editor_cx->bottom_line_y;
int view_y = editor_cx->viewport_org_y;
int view_h = editor_cx->viewport_height;
view_y *= lineHeight();
view_h *= lineHeight();
FillLineNumberBG(view_y, view_h, theme()->bgLineNumber());
paintLineNumbers([&](int y, QString text, Document::Line const *line){
if (bottom >= 0 && y > bottom) return;
if (isCursorVisible() && y == visualY(editor_cx.get())) {
FillLineNumberBG(y * lineHeight(), lineHeight(), theme()->bgCurrentLineNumber());
}
pr.setBackground(Qt::transparent);
pr.setPen(theme()->fgLineNumber());
drawText(&pr, 0, y * lineHeight(), text);
if (line) {
char const *mark = nullptr;
if (line->type == Document::Line::Add) {
mark = "+";
} else if (line->type == Document::Line::Del) {
mark = "-";
}
if (mark) {
pr.setPen(theme()->fgDefault());
drawText(&pr, linenum_width - defaultCharWidth() * 3 / 2, y * lineHeight(), mark);
}
}
});
if (linenum_width > 0) {
pr.fillRect(0, view_y, 1, view_h, Qt::black);
}
pr.fillRect(linenum_width - 2, view_y, 1, view_h, Qt::black);
if (bottom >= 0) {
int y = (editor_cx->viewport_org_y + bottom) * lineHeight() + 1;
pr.fillRect(0, y, width(), 1, Qt::black);
}
}
+ // フォーカス枠描画
if (m->is_focus_frame_visible && has_focus) {
drawFocusFrame(&pr);
}
}
void TextEditorWidget::moveCursorByMouse()
{
QPoint mousepos = mapFromGlobal(QCursor::pos());
QPoint pos = mapFromPixel(mousepos);
int row = pos.y();
int col = pos.x();
// row
if (!isSingleLineMode()) {
if (row < 0) {
row = 0;
} else {
int maxrow = cx()->engine->document.lines.size();
maxrow = maxrow > 0 ? (maxrow - 1) : 0;
if (row > maxrow) {
row = maxrow;
}
}
}
setCursorRow(row, false, true);
setCursorCol(col, false, true);
clearParsedLine();
updateVisibility(false, true, false);
}
void TextEditorWidget::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::RightButton) return;
savePos();
bool shift = (event->modifiers() & Qt::ShiftModifier);
if (shift) {
if (isSelectionAnchorEnabled()) {
setSelectionAnchor(SelectionAnchor::EnabledEasy, false, false);
} else {
setSelectionAnchor(SelectionAnchor::EnabledEasy, true, false);
}
}
moveCursorByMouse();
if (shift) {
setSelectionAnchor(SelectionAnchor::EnabledEasy, false, false);
} else {
setSelectionAnchor(SelectionAnchor::EnabledEasy, true, false);
}
selection_anchor_1 = selection_anchor_0;
if (isTerminalMode()) {
clearParsedLine();
restorePos();
}
}
void TextEditorWidget::mouseReleaseEvent(QMouseEvent * /*event*/)
{
}
void TextEditorWidget::mouseMoveEvent(QMouseEvent * /*event*/)
{
savePos();
moveCursorByMouse();
setSelectionAnchor(SelectionAnchor::EnabledEasy, true, false);
if (isTerminalMode()) {
clearParsedLine();
restorePos();
}
}
QVariant TextEditorWidget::inputMethodQuery(Qt::InputMethodQuery q) const
{
if (q == Qt::ImCursorRectangle) {
QRect r = cx()->cursor_rect;
return r;
}
return QWidget::inputMethodQuery(q);
}
void TextEditorWidget::inputMethodEvent(QInputMethodEvent *e)
{
#ifdef Q_OS_WIN
PreEditText preedit;
preedit.text = e->preeditString();
for (QInputMethodEvent::Attribute const &a : e->attributes()) {
if (a.type == QInputMethodEvent::TextFormat) {
QTextFormat f = qvariant_cast<QTextFormat>(a.value);
if (f.type() == QTextFormat::CharFormat) {
preedit.format.emplace_back(a.start, a.length, f);
} else {
}
}
}
if (preedit.text.isEmpty()) {
// m->ime_popup->hide();
} else {
QPoint pt = mapToGlobal(cx()->cursor_rect.topLeft());
// m->ime_popup->move(pt);
// m->ime_popup->setFont(font());
// m->ime_popup->setPreEditText(preedit);
// m->ime_popup->show();
}
#endif
// qDebug() << e->preeditString() << e->commitString();
QString const &commit_text = e->commitString();
if (!commit_text.isEmpty()) {
write_(commit_text, true);
}
}
void TextEditorWidget::refrectScrollBar()
{
int v = m->scroll_bar_v ? m->scroll_bar_v->value() : -1;
int h = m->scroll_bar_h ? m->scroll_bar_h->value() : -1;
move(-1, -1, v, h, false);
}
void TextEditorWidget::layoutEditor()
{
if (isAutoLayout()) {
int h = height() / lineHeight();
int w = width() / defaultCharWidth();
setScreenSize(w, h, false);
}
AbstractTextEditorApplication::layoutEditor();
}
void TextEditorWidget::resizeEvent(QResizeEvent * /*event*/)
{
if (isAutoLayout()) {
layoutEditor();
}
internalUpdateScrollBar();
}
void TextEditorWidget::wheelEvent(QWheelEvent *event)
{
int pos = 0;
m->wheel_delta += event->delta();
while (m->wheel_delta >= 40) {
m->wheel_delta -= 40;
pos--;
}
while (m->wheel_delta <= -40) {
m->wheel_delta += 40;
pos++;
}
if (m->scroll_bar_v) {
m->scroll_bar_v->setValue(m->scroll_bar_v->value() + pos);
}
}
void TextEditorWidget::setFocusFrameVisible(bool f)
{
m->is_focus_frame_visible = f;
}
void TextEditorWidget::timerEvent(QTimerEvent *)
{
if (!isChanged()) {
m->idle_count++;
if (m->idle_count >= 10) {
m->idle_count = 0;
emit idle();
}
}
}
void TextEditorWidget::contextMenuEvent(QContextMenuEvent *event)
{
QMenu menu;
QAction *a_cut = nullptr;
if (!isReadOnly() && !isTerminalMode()) a_cut = menu.addAction("Cut");
QAction *a_copy = menu.addAction("Copy");
QAction *a_paste = nullptr;
if (!isReadOnly() && !isTerminalMode()) a_paste = menu.addAction("Paste");
QAction *a = menu.exec(misc::contextMenuPos(this, event));
if (a) {
if (a == a_cut) {
editCut();
return;
}
if (a == a_copy) {
editCopy();
return;
}
if (a == a_paste) {
editPaste();
return;
}
}
}
diff --git a/src/texteditor/TextEditorWidget.h b/src/texteditor/TextEditorWidget.h
index 4bf9e67..4466f80 100644
--- a/src/texteditor/TextEditorWidget.h
+++ b/src/texteditor/TextEditorWidget.h
@@ -1,104 +1,105 @@
#ifndef OREWIDGET_H
#define OREWIDGET_H
#include <QTextFormat>
#include <QWidget>
#include <vector>
#include <cstdint>
#include <memory>
#include "AbstractCharacterBasedApplication.h"
#include "TextEditorTheme.h"
class QScrollBar;
struct PreEditText {
struct Format {
int start;
int length;
QTextFormat format;
Format(int start, int length, QTextFormat const &f)
: start(start)
, length(length)
, format(f)
{
}
};
QString text;
std::vector<Format> format;
};
class TextEditorWidget : public QWidget, public AbstractTextEditorApplication {
Q_OBJECT
public:
private:
struct Private;
Private *m;
void paintScreen(QPainter *painter);
void drawCursor(QPainter *pr);
void drawFocusFrame(QPainter *pr);
QRect updateCursorRect(bool auto_scroll);
QColor defaultForegroundColor();
QColor defaultBackgroundColor();
QColor colorForIndex(CharAttr const &attr, bool foreground);
void internalUpdateVisibility(bool ensure_current_line_visible, bool change_col, bool auto_scroll);
void internalUpdateScrollBar();
void moveCursorByMouse();
void setTextFont(const QFont &font);
- int parseLine3(int row, std::vector<Char> *vec, int *last_pos) const;
+ int parseLine3(int row, int col, std::vector<Char> *vec) const;
+ int xScrollPosInPixel();
public:
int defaultCharWidth() const;
protected:
void paintEvent(QPaintEvent *) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void wheelEvent(QWheelEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
void contextMenuEvent(QContextMenuEvent *event) override;
QFont textFont() const;
void drawText(QPainter *painter, int px, int py, QString const &str);
public:
explicit TextEditorWidget(QWidget *parent = nullptr);
~TextEditorWidget() override;
void setTheme(const TextEditorThemePtr &theme);
TextEditorTheme const *theme() const;
int charWidth2(unsigned int c) const;
int lineHeight() const;
// void setPreEditText(PreEditText const &preedit);
void updateVisibility(bool ensure_current_line_visible, bool change_col, bool auto_scroll) override;
bool event(QEvent *event) override;
void bindScrollBar(QScrollBar *vsb, QScrollBar *hsb);
void setupForLogWidget(QScrollBar *vsb, QScrollBar *hsb, const TextEditorThemePtr &theme);
QPoint mapFromPixel(const QPoint &pt);
QPoint mapToPixel(const QPoint &pt);
QVariant inputMethodQuery(Qt::InputMethodQuery q) const override;
void inputMethodEvent(QInputMethodEvent *e) override;
void refrectScrollBar();
void setRenderingMode(RenderingMode mode);
void move(int cur_row, int cur_col, int scr_row, int scr_col, bool auto_scroll);
void layoutEditor() override;
void setFocusFrameVisible(bool f);
signals:
void moved(int cur_row, int cur_col, int scr_row, int scr_col);
void updateScrollBar();
void idle();
protected:
void timerEvent(QTimerEvent *) override;
};
#endif // OREWIDGET_H
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Feb 7, 1:29 PM (9 h, 6 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
55977
Default Alt Text
(89 KB)
Attached To
Mode
R77 Guitar
Attached
Detach File
Event Timeline
Log In to Comment