Skip to content

Commit 130318d

Browse files
author
Paweł Salawa
committed
#5713 MultiEditor now keeps original line separators from the value loaded, while new separators inserted are alwas \n. Also optimized performance for very long text lines in the editor.
1 parent 136e407 commit 130318d

7 files changed

Lines changed: 177 additions & 70 deletions

File tree

ChangeLog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@
118118
- BUGFIX: #5628 Fixed PDF export bugs (first page size too small, occasional table overlapping, page numbers cut off).
119119
- BUGFIX: #5691 Fixed tooltip in Database Dialog with Wayland under Linux.
120120
- BUGFIX: #5692 Select first inserted row after inserting multiple rows in Grid View.
121+
- BUGFIX: #5713 MultiEditor now keeps original line separators from the value loaded, while new separators inserted are alwas \n. Also optimized performance for very long text lines in the editor.
121122

122123
### 3.4.21
123124
- ADDED: #5466 Support foreign keys with implicit columns (proper values in FK combobox columns).

Letos/gui/dialogs/configdialog.ui

Lines changed: 46 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@
267267
</sizepolicy>
268268
</property>
269269
<property name="currentIndex">
270-
<number>4</number>
270+
<number>1</number>
271271
</property>
272272
<widget class="QWidget" name="databaseListPage">
273273
<layout class="QVBoxLayout" name="verticalLayout_36">
@@ -487,8 +487,8 @@
487487
<rect>
488488
<x>0</x>
489489
<y>0</y>
490-
<width>439</width>
491-
<height>882</height>
490+
<width>679</width>
491+
<height>854</height>
492492
</rect>
493493
</property>
494494
<layout class="QVBoxLayout" name="verticalLayout_34">
@@ -498,36 +498,52 @@
498498
<string>Data browsing and editing</string>
499499
</property>
500500
<layout class="QGridLayout" name="gridLayout_2">
501-
<item row="0" column="0" colspan="2">
502-
<widget class="QLabel" name="rowsPerPageLabel">
501+
<item row="2" column="2">
502+
<widget class="QSpinBox" name="memPopulConfigsCheck">
503+
<property name="toolTip">
504+
<string>&lt;p&gt;Maximum number of configurations of Populate Table dialog stored in configuration. Value of 100 should be sufficient.&lt;/p&gt;</string>
505+
</property>
506+
<property name="maximum">
507+
<number>999999</number>
508+
</property>
509+
<property name="cfg" stdset="0">
510+
<string notr="true">General.PopulateHistorySize</string>
511+
</property>
512+
</widget>
513+
</item>
514+
<item row="2" column="0" colspan="2">
515+
<widget class="QLabel" name="memPopulConfigsLabel">
516+
<property name="toolTip">
517+
<string>&lt;p&gt;Maximum number of configurations of Populate Table dialog stored in configuration. Value of 100 should be sufficient.&lt;/p&gt;</string>
518+
</property>
503519
<property name="text">
504-
<string>Number of data rows per page:</string>
520+
<string>Number of memorized table populating configurations</string>
505521
</property>
506522
</widget>
507523
</item>
508-
<item row="6" column="0" colspan="3">
509-
<widget class="QCheckBox" name="useLfForMultilineBox">
524+
<item row="4" column="0" colspan="3">
525+
<widget class="QCheckBox" name="keepNullWhenEmptyCheck">
510526
<property name="toolTip">
511-
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Converts Unicode line separators (U+2028, U+2029) to the standard LF (&lt;span style=&quot; font-family:'monospace';&quot;&gt;\n&lt;/span&gt;) character when editing text in form view editor.&lt;/p&gt;&lt;p&gt;Disable to preserve the original characters.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
527+
<string>&lt;p&gt;When editing a cell which used to have NULL value and entering empty string as new value, then this option determinates whether the new value should remain NULL (have this option enabled), or should it be overwritten with empty string value (have this option disabled).&lt;/p&gt;</string>
512528
</property>
513529
<property name="text">
514-
<string>Convert Unicode line separators to LF (\n) in form view editors</string>
530+
<string>Keep NULL value when entering empty value</string>
515531
</property>
516532
<property name="cfg" stdset="0">
517-
<string notr="true">General.UseLfForMultilineEditors</string>
533+
<string notr="true">General.KeepNullWhenEmptyValue</string>
518534
</property>
519535
</widget>
520536
</item>
521-
<item row="5" column="0" colspan="3">
522-
<widget class="QCheckBox" name="useSciFormatForDoublesCheck">
537+
<item row="7" column="0">
538+
<widget class="QCheckBox" name="editCellAfterAdvancingCheck">
523539
<property name="toolTip">
524-
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When enabled, Letos automatically switches to scientific notation for very small real numbers (e.g. &lt;span style=&quot; font-style:italic;&quot;&gt;5.3e-21&lt;/span&gt;).&lt;br/&gt;Decimal notation is used for values whose magnitude allows a readable decimal representation.&lt;/p&gt;&lt;p&gt;When disabled, real numbers are always displayed using decimal notation, even if this results in very long fractional parts.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
540+
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Automatically starts editing the cell in the next row after moving to it with Enter.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
525541
</property>
526542
<property name="text">
527-
<string>Use scientific notation for very small real numbers in the grid view</string>
543+
<string>Start editing after moving to the next row</string>
528544
</property>
529545
<property name="cfg" stdset="0">
530-
<string notr="true">General.UseSciFormatForDoubles</string>
546+
<string notr="true">General.DataEditAutoAdvanceEdit</string>
531547
</property>
532548
</widget>
533549
</item>
@@ -550,7 +566,7 @@
550566
</property>
551567
</widget>
552568
</item>
553-
<item row="7" column="0">
569+
<item row="6" column="0">
554570
<widget class="QCheckBox" name="advanceRowOnEnterCheck">
555571
<property name="toolTip">
556572
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When editing a cell, pressing Enter accepts the value and moves to the same column in the next row.&lt;br/&gt;If disabled, the selection stays on the current cell.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
@@ -563,39 +579,23 @@
563579
</property>
564580
</widget>
565581
</item>
566-
<item row="3" column="0" colspan="3">
567-
<widget class="QCheckBox" name="tolltipInDataViewCheck">
568-
<property name="toolTip">
569-
<string>&lt;p&gt;When this is enabled and user holds mouse pointer over a cell in any data view (query results, a table data, a view data) a tooltip will appear with details about the cell - it includes details like column data type, constraints, ROWID and others.&lt;/p&gt;</string>
570-
</property>
582+
<item row="0" column="0" colspan="2">
583+
<widget class="QLabel" name="rowsPerPageLabel">
571584
<property name="text">
572-
<string>Show column and row details tooltip in data view</string>
573-
</property>
574-
<property name="cfg" stdset="0">
575-
<string notr="true">General.ShowDataViewTooltips</string>
585+
<string>Number of data rows per page:</string>
576586
</property>
577587
</widget>
578588
</item>
579-
<item row="2" column="0" colspan="2">
580-
<widget class="QLabel" name="memPopulConfigsLabel">
589+
<item row="5" column="0" colspan="3">
590+
<widget class="QCheckBox" name="useSciFormatForDoublesCheck">
581591
<property name="toolTip">
582-
<string>&lt;p&gt;Maximum number of configurations of Populate Table dialog stored in configuration. Value of 100 should be sufficient.&lt;/p&gt;</string>
592+
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When enabled, Letos automatically switches to scientific notation for very small real numbers (e.g. &lt;span style=&quot; font-style:italic;&quot;&gt;5.3e-21&lt;/span&gt;).&lt;br/&gt;Decimal notation is used for values whose magnitude allows a readable decimal representation.&lt;/p&gt;&lt;p&gt;When disabled, real numbers are always displayed using decimal notation, even if this results in very long fractional parts.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
583593
</property>
584594
<property name="text">
585-
<string>Number of memorized table populating configurations</string>
586-
</property>
587-
</widget>
588-
</item>
589-
<item row="2" column="2">
590-
<widget class="QSpinBox" name="memPopulConfigsCheck">
591-
<property name="toolTip">
592-
<string>&lt;p&gt;Maximum number of configurations of Populate Table dialog stored in configuration. Value of 100 should be sufficient.&lt;/p&gt;</string>
593-
</property>
594-
<property name="maximum">
595-
<number>999999</number>
595+
<string>Use scientific notation for very small real numbers in the grid view</string>
596596
</property>
597597
<property name="cfg" stdset="0">
598-
<string notr="true">General.PopulateHistorySize</string>
598+
<string notr="true">General.UseSciFormatForDoubles</string>
599599
</property>
600600
</widget>
601601
</item>
@@ -612,29 +612,16 @@
612612
</property>
613613
</widget>
614614
</item>
615-
<item row="4" column="0" colspan="3">
616-
<widget class="QCheckBox" name="keepNullWhenEmptyCheck">
617-
<property name="toolTip">
618-
<string>&lt;p&gt;When editing a cell which used to have NULL value and entering empty string as new value, then this option determinates whether the new value should remain NULL (have this option enabled), or should it be overwritten with empty string value (have this option disabled).&lt;/p&gt;</string>
619-
</property>
620-
<property name="text">
621-
<string>Keep NULL value when entering empty value</string>
622-
</property>
623-
<property name="cfg" stdset="0">
624-
<string notr="true">General.KeepNullWhenEmptyValue</string>
625-
</property>
626-
</widget>
627-
</item>
628-
<item row="8" column="0">
629-
<widget class="QCheckBox" name="editCellAfterAdvancingCheck">
615+
<item row="3" column="0" colspan="3">
616+
<widget class="QCheckBox" name="tolltipInDataViewCheck">
630617
<property name="toolTip">
631-
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Automatically starts editing the cell in the next row after moving to it with Enter.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
618+
<string>&lt;p&gt;When this is enabled and user holds mouse pointer over a cell in any data view (query results, a table data, a view data) a tooltip will appear with details about the cell - it includes details like column data type, constraints, ROWID and others.&lt;/p&gt;</string>
632619
</property>
633620
<property name="text">
634-
<string>Start editing after moving to the next row</string>
621+
<string>Show column and row details tooltip in data view</string>
635622
</property>
636623
<property name="cfg" stdset="0">
637-
<string notr="true">General.DataEditAutoAdvanceEdit</string>
624+
<string notr="true">General.ShowDataViewTooltips</string>
638625
</property>
639626
</widget>
640627
</item>

Letos/gui/multieditor/multieditorhex.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
#include "multieditorhex.h"
22
#include "qhexedit2/qhexedit.h"
3+
#include "style.h"
34
#include <QVBoxLayout>
5+
#include <QBrush>
46

57
MultiEditorHex::MultiEditorHex()
68
{
79
setLayout(new QVBoxLayout());
810
hexEdit = new QHexEdit();
11+
hexEdit->setAddressAreaColor(STYLE->extendedPalette().editorLineNumberBase().color());
12+
hexEdit->setHighlightingColor(STYLE->standardPalette().highlight().color());
13+
hexEdit->setSelectionColor(STYLE->standardPalette().highlight().color());
914
layout()->addWidget(hexEdit);
1015

1116
connect(hexEdit, SIGNAL(dataChanged()), this, SLOT(modificationChanged()));

Letos/gui/multieditor/multieditortext.cpp

Lines changed: 115 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "multieditortext.h"
2+
#include "diff/diff_match_patch.h"
23
#include "iconmanager.h"
34
#include "uiconfig.h"
45
#include <QPlainTextEdit>
@@ -15,6 +16,7 @@ MultiEditorText::MultiEditorText(QWidget *parent) :
1516
{
1617
setLayout(new QVBoxLayout());
1718
textEdit = new QPlainTextEdit();
19+
textEdit->setLineWrapMode(QPlainTextEdit::NoWrap); // Disable line wrapping for performance with long lines (1MB single line freezes app for several seconds)
1820
layout()->addWidget(textEdit);
1921
initActions();
2022
setupMenu();
@@ -30,19 +32,13 @@ MultiEditorText::MultiEditorText(QWidget *parent) :
3032

3133
void MultiEditorText::setValue(const QVariant& value)
3234
{
33-
textEdit->setPlainText(value.toString());
35+
originalText = value.toString();
36+
textEdit->setPlainText(originalText);
3437
}
3538

3639
QVariant MultiEditorText::getValue()
3740
{
38-
if (CFG_UI.General.UseLfForMultilineEditors.get())
39-
{
40-
QString newStr = textEdit->document()->toRawText();
41-
newStr.replace(QChar::ParagraphSeparator, '\n');
42-
newStr.replace(QChar::LineSeparator, '\n');
43-
return newStr;
44-
}
45-
return textEdit->document()->toRawText();
41+
return getValueFromPlainTextEdit(originalText, textEdit);
4642
}
4743

4844
void MultiEditorText::setReadOnly(bool value)
@@ -76,6 +72,17 @@ SearchTextLocator *MultiEditorText::getTextLocator()
7672
return textLocator;
7773
}
7874

75+
QString MultiEditorText::getValueFromPlainTextEdit(const QString& originalText, QPlainTextEdit* textEdit)
76+
{
77+
QString newText = textEdit->document()->toRawText();
78+
if (originalText.contains(QChar::ParagraphSeparator) || originalText.contains(QChar::LineSeparator))
79+
return restoreOriginalSeparatorsWithDiff(originalText, newText);
80+
81+
newText.replace(QChar::ParagraphSeparator, '\n');
82+
newText.replace(QChar::LineSeparator, '\n');
83+
return newText;
84+
}
85+
7986
void MultiEditorText::modificationChanged(bool changed)
8087
{
8188
if (changed)
@@ -157,6 +164,105 @@ void MultiEditorText::setupMenu()
157164
contextMenu->addAction(actionMap[DELETE]);
158165
}
159166

167+
bool MultiEditorText::isOriginalOrEditorLineBreak(QChar ch)
168+
{
169+
return ch == QChar('\n') ||
170+
ch == QChar::LineSeparator ||
171+
ch == QChar::ParagraphSeparator;
172+
}
173+
174+
bool MultiEditorText::isQTextDocumentSeparator(QChar ch)
175+
{
176+
return ch == QChar::LineSeparator ||
177+
ch == QChar::ParagraphSeparator;
178+
}
179+
180+
QString MultiEditorText::normalizedForDiff(QString text)
181+
{
182+
for (int i = 0; i < text.size(); ++i)
183+
{
184+
if (isOriginalOrEditorLineBreak(text.at(i)))
185+
text[i] = QChar('\n');
186+
}
187+
188+
return text;
189+
}
190+
191+
QVector<int> MultiEditorText::buildCurrentToOriginalMap(const QString& originalText, const QString& currentText)
192+
{
193+
QString normalizedOriginal = normalizedForDiff(originalText);
194+
QString normalizedCurrent = normalizedForDiff(currentText);
195+
QVector<int> currentToOriginal(currentText.size(), -1);
196+
197+
diff_match_patch dmp;
198+
int originalPos = 0;
199+
int currentPos = 0;
200+
auto diffs = dmp.diff_main(normalizedOriginal, normalizedCurrent, false);
201+
for (const Diff& diff : std::as_const(diffs))
202+
{
203+
int len = diff.text.size();
204+
switch (diff.operation)
205+
{
206+
case Operation::EQUAL:
207+
{
208+
for (int i = 0; i < len; ++i)
209+
{
210+
if (currentPos + i < currentToOriginal.size())
211+
currentToOriginal[currentPos + i] = originalPos + i;
212+
}
213+
originalPos += len;
214+
currentPos += len;
215+
break;
216+
}
217+
case Operation::DELETE:
218+
{
219+
originalPos += len;
220+
break;
221+
}
222+
case Operation::INSERT:
223+
{
224+
currentPos += len;
225+
break;
226+
}
227+
}
228+
}
229+
230+
return currentToOriginal;
231+
}
232+
233+
QString MultiEditorText::restoreOriginalSeparatorsWithDiff(const QString& originalText, const QString& currentRawText)
234+
{
235+
QString result;
236+
237+
QVector<int> currentToOriginal = buildCurrentToOriginalMap(originalText, currentRawText);
238+
result.reserve(currentRawText.size());
239+
for (int i = 0; i < currentRawText.size(); ++i)
240+
{
241+
const QChar currentChar = currentRawText.at(i);
242+
if (!isQTextDocumentSeparator(currentChar))
243+
{
244+
result += currentChar;
245+
continue;
246+
}
247+
248+
const int originalPos = currentToOriginal.value(i, -1);
249+
if (originalPos >= 0 && originalPos < originalText.size())
250+
{
251+
const QChar originalChar = originalText.at(originalPos);
252+
if (originalChar == QChar::LineSeparator)
253+
result += QChar::LineSeparator;
254+
else if (originalChar == QChar::ParagraphSeparator)
255+
result += QChar::ParagraphSeparator;
256+
else
257+
result += QChar('\n');
258+
}
259+
else
260+
result += QChar('\n');
261+
}
262+
263+
return result;
264+
}
265+
160266
MultiEditorWidget* MultiEditorTextPlugin::getInstance()
161267
{
162268
return new MultiEditorText();

Letos/gui/multieditor/multieditortext.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,25 @@ class GUI_API_EXPORT MultiEditorText : public MultiEditorWidget, public ExtActio
4949
QList<QWidget*> getNoScrollWidgets();
5050
SearchTextLocator* getTextLocator();
5151

52+
static QString getValueFromPlainTextEdit(const QString& originalText, QPlainTextEdit* textEdit);
53+
static QString restoreOriginalSeparatorsWithDiff(const QString& originalText, const QString& currentRawText);
54+
5255
protected:
5356
void createActions();
5457
void setupDefShortcuts();
5558

5659
private:
5760
void setupMenu();
5861

62+
static bool isOriginalOrEditorLineBreak(QChar ch);
63+
static bool isQTextDocumentSeparator(QChar ch);
64+
static QString normalizedForDiff(QString text);
65+
static QVector<int> buildCurrentToOriginalMap(const QString& originalText, const QString& currentText);
66+
5967
QPlainTextEdit* textEdit = nullptr;
6068
QMenu* contextMenu = nullptr;
6169
SearchTextLocator* textLocator = nullptr;
70+
QString originalText;
6271

6372
private slots:
6473
void modificationChanged(bool changed);

Letos/gui/uiconfig.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,6 @@ CFG_UI_CATEGORIES(Ui,
177177
CFG_ENTRY(bool, ShowDataViewTooltips, true)
178178
CFG_ENTRY(bool, KeepNullWhenEmptyValue, true)
179179
CFG_ENTRY(bool, UseSciFormatForDoubles, false)
180-
CFG_ENTRY(bool, UseLfForMultilineEditors, false)
181180
CFG_ENTRY(Cfg::ColorPickerConfig, ColorPickerConfig, Cfg::ColorPickerConfig())
182181
CFG_ENTRY(int, ToolBarIconSize, 0)
183182
CFG_ENTRY(QString, LastUsedConfigPage, QString())

0 commit comments

Comments
 (0)