Skip to content

Commit 149a6ad

Browse files
committed
add "Copy as HTML" menu item
1 parent 7d42260 commit 149a6ad

8 files changed

Lines changed: 124 additions & 2 deletions

QtSLiM/QtSLiMAppDelegate.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,6 +1018,12 @@ void QtSLiMAppDelegate::addActionsForGlobalMenuItems(QWidget *window)
10181018
window->addAction(actionExecuteAll);
10191019
}
10201020

1021+
{
1022+
QAction *actionCopyAsHTML = new QAction("Copy as HTML", this);
1023+
actionCopyAsHTML->setShortcut(flagsAndKey(Qt::ControlModifier | Qt::AltModifier, Qt::Key_C));
1024+
connect(actionCopyAsHTML, &QAction::triggered, qtSLiMAppDelegate, &QtSLiMAppDelegate::dispatch_copyAsHTML);
1025+
window->addAction(actionCopyAsHTML);
1026+
}
10211027
{
10221028
QAction *actionShiftLeft = new QAction("Shift Left", this);
10231029
actionShiftLeft->setShortcut(flagsAndKey(Qt::ControlModifier, Qt::Key_BracketLeft));
@@ -1377,6 +1383,15 @@ void QtSLiMAppDelegate::dispatch_close(void)
13771383
qApp->beep();
13781384
}
13791385

1386+
void QtSLiMAppDelegate::dispatch_copyAsHTML(void)
1387+
{
1388+
QWidget *focusWidget = QApplication::focusWidget();
1389+
QtSLiMScriptTextEdit *scriptEdit = dynamic_cast<QtSLiMScriptTextEdit*>(focusWidget);
1390+
1391+
if (scriptEdit && scriptEdit->isEnabled())
1392+
scriptEdit->copyAsHTML();
1393+
}
1394+
13801395
void QtSLiMAppDelegate::dispatch_shiftLeft(void)
13811396
{
13821397
QWidget *focusWidget = QApplication::focusWidget();

QtSLiM/QtSLiMAppDelegate.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ public slots:
125125
void dispatch_open(void);
126126
void dispatch_close(void);
127127

128+
void dispatch_copyAsHTML(void);
128129
void dispatch_shiftLeft(void);
129130
void dispatch_shiftRight(void);
130131
void dispatch_commentUncomment(void);

QtSLiM/QtSLiMScriptTextEdit.cpp

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
#include <QTextDocument>
4040
#include <QMenu>
4141
#include <QToolTip>
42+
#include <QClipboard>
43+
#include <QMimeData>
4244
#include <QDebug>
4345

4446
#include <utility>
@@ -2625,9 +2627,23 @@ QStringList QtSLiMScriptTextEdit::linesForRoundedSelection(QTextCursor &p_cursor
26252627
#endif
26262628
}
26272629

2630+
void QtSLiMScriptTextEdit::copyAsHTML(void)
2631+
{
2632+
if (isEnabled())
2633+
{
2634+
QString html = exportAsHtml();
2635+
QClipboard *clipboard = QGuiApplication::clipboard();
2636+
QMimeData *mimeData = new QMimeData;
2637+
2638+
mimeData->setHtml(html);
2639+
mimeData->setText(html);
2640+
clipboard->setMimeData(mimeData);
2641+
}
2642+
}
2643+
26282644
void QtSLiMScriptTextEdit::shiftSelectionLeft(void)
26292645
{
2630-
if (isEnabled() && !isReadOnly())
2646+
if (isEnabled() && !isReadOnly())
26312647
{
26322648
QTextCursor &&edit_cursor = textCursor();
26332649
bool movedBack;
@@ -3303,6 +3319,77 @@ void QtSLiMScriptTextEdit::addScriptBlockColoring(int startPos, int endPos, Spec
33033319
blockSpecies.emplace_back(species);
33043320
}
33053321

3322+
// this method courtesy of SO user Larswad, https://stackoverflow.com/a/15808889/2752221
3323+
QString QtSLiMScriptTextEdit::exportAsHtml(void)
3324+
{
3325+
// Create a new document from the entire document
3326+
QTextCursor cursor(document());
3327+
cursor.select(QTextCursor::Document);
3328+
QTextDocument *tempDocument = new QTextDocument;
3329+
QTextCursor tempCursor(tempDocument);
3330+
3331+
tempCursor.insertFragment(cursor.selection());
3332+
tempCursor.select(QTextCursor::Document);
3333+
3334+
// Set the default foreground for the inserted characters
3335+
QTextCharFormat textfmt = tempCursor.charFormat();
3336+
textfmt.setForeground(Qt::black);
3337+
tempCursor.setCharFormat(textfmt);
3338+
3339+
// Apply the additional formats set by the syntax highlighter
3340+
QTextBlock start = document()->findBlock(cursor.selectionStart());
3341+
QTextBlock end = document()->findBlock(cursor.selectionEnd());
3342+
end = end.next();
3343+
const int selectionStart = cursor.selectionStart();
3344+
const int endOfDocument = tempDocument->characterCount() - 1;
3345+
for (QTextBlock current = start; current.isValid() and current not_eq end; current = current.next()) {
3346+
const QTextLayout* layout(current.layout());
3347+
3348+
foreach (const QTextLayout::FormatRange &range, layout->formats()) {
3349+
const int formatStart = current.position() + range.start - selectionStart;
3350+
const int formatEnd = formatStart + range.length;
3351+
if(formatEnd <= 0 or formatStart >= endOfDocument)
3352+
continue;
3353+
tempCursor.setPosition(qMax(formatStart, 0));
3354+
tempCursor.setPosition(qMin(formatEnd, endOfDocument), QTextCursor::KeepAnchor);
3355+
tempCursor.setCharFormat(range.format);
3356+
}
3357+
}
3358+
3359+
// Reset the user states since they are not interesting
3360+
for (QTextBlock block = tempDocument->begin(); block.isValid(); block = block.next())
3361+
block.setUserState(-1);
3362+
3363+
// Make sure the text appears pre-formatted, and set the background we want
3364+
tempCursor.select(QTextCursor::Document);
3365+
QTextBlockFormat blockFormat = tempCursor.blockFormat();
3366+
blockFormat.setNonBreakableLines(true);
3367+
//blockFormat.setBackground(Qt::black);
3368+
tempCursor.setBlockFormat(blockFormat);
3369+
3370+
// Select the same range with tempCursor as is selected in the original document
3371+
QTextCursor selectedRange = textCursor();
3372+
tempCursor.setPosition(selectedRange.anchor(), QTextCursor::MoveAnchor);
3373+
tempCursor.setPosition(selectedRange.position(), QTextCursor::KeepAnchor);
3374+
3375+
// Retrieve the syntax highlighted and formatted html
3376+
QString html = tempCursor.selection().toHtml();
3377+
delete tempDocument;
3378+
3379+
// There is a bug where the first line uses <p> instead of <pre>, because
3380+
// it is a fragment, I guess. We can fix that with a regex.
3381+
QRegularExpression pToPreRegex("<p style=(.*)</p>");
3382+
pToPreRegex.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
3383+
html.replace(pToPreRegex, "<pre style=\\1</pre>");
3384+
3385+
// Change new paragraphs to new lines, so we get one paragraph of text
3386+
// This doesn't work, you always get a new paragraph for each line; <BR> doesn't work either.
3387+
//QRegularExpression preRegex("</pre>.?.?<pre style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">");
3388+
//preRegex.setPatternOptions(QRegularExpression::DotMatchesEverythingOption | QRegularExpression::CaseInsensitiveOption);
3389+
//html.replace(preRegex, "\n");
3390+
3391+
return html;
3392+
}
33063393

33073394

33083395

QtSLiM/QtSLiMScriptTextEdit.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,10 @@ class QtSLiMScriptTextEdit : public QtSLiMTextEdit
195195
void clearScriptBlockColoring(void);
196196
void addScriptBlockColoring(int startPos, int endPos, Species *species);
197197

198+
QString exportAsHtml(void);
199+
198200
public slots:
201+
void copyAsHTML(void);
199202
void shiftSelectionLeft(void);
200203
void shiftSelectionRight(void);
201204
void commentUncommentSelection(void);

QtSLiM/QtSLiMWindow.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3090,8 +3090,10 @@ void QtSLiMWindow::updateMenuEnablingSHARED(QWidget *p_focusWidget)
30903090

30913091
// actions handled by QtSLiMScriptTextEdit only
30923092
QtSLiMScriptTextEdit *scriptEdit = dynamic_cast<QtSLiMScriptTextEdit*>(p_focusWidget);
3093-
bool isModifiableScriptTextEdit = (scriptEdit && !scriptEdit->isReadOnly());
3093+
bool isScriptTextEdit = (!!scriptEdit);
3094+
bool isModifiableScriptTextEdit = (isScriptTextEdit && !scriptEdit->isReadOnly());
30943095

3096+
ui->actionCopyAsHTML->setEnabled(isScriptTextEdit);
30953097
ui->actionShiftLeft->setEnabled(isModifiableScriptTextEdit);
30963098
ui->actionShiftRight->setEnabled(isModifiableScriptTextEdit);
30973099
ui->actionCommentUncomment->setEnabled(isModifiableScriptTextEdit);

QtSLiM/QtSLiMWindow.ui

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1352,6 +1352,7 @@ QSlider::handle:horizontal:disabled {
13521352
<addaction name="separator"/>
13531353
<addaction name="actionCut"/>
13541354
<addaction name="actionCopy"/>
1355+
<addaction name="actionCopyAsHTML"/>
13551356
<addaction name="actionPaste"/>
13561357
<addaction name="actionDelete"/>
13571358
<addaction name="actionSelectAll"/>
@@ -2036,6 +2037,17 @@ QSlider::handle:horizontal:disabled {
20362037
<string>Show nonWF Tick Cycle (Multispecies)</string>
20372038
</property>
20382039
</action>
2040+
<action name="actionCopyAsHTML">
2041+
<property name="text">
2042+
<string>Copy as HTML</string>
2043+
</property>
2044+
<property name="toolTip">
2045+
<string>Copy as HTML</string>
2046+
</property>
2047+
<property name="shortcut">
2048+
<string>Ctrl+Alt+C</string>
2049+
</property>
2050+
</action>
20392051
</widget>
20402052
<layoutdefault spacing="6" margin="11"/>
20412053
<customwidgets>

QtSLiM/QtSLiMWindow_glue.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ void QtSLiMWindow::glueUI(void)
208208
connect(ui->actionAboutStickSoftware, &QAction::triggered, qtSLiMAppDelegate, &QtSLiMAppDelegate::dispatch_helpStickSoftware);
209209

210210
// connect custom menu items
211+
connect(ui->actionCopyAsHTML, &QAction::triggered, qtSLiMAppDelegate, &QtSLiMAppDelegate::dispatch_copyAsHTML);
211212
connect(ui->actionShiftLeft, &QAction::triggered, qtSLiMAppDelegate, &QtSLiMAppDelegate::dispatch_shiftLeft);
212213
connect(ui->actionShiftRight, &QAction::triggered, qtSLiMAppDelegate, &QtSLiMAppDelegate::dispatch_shiftRight);
213214
connect(ui->actionCommentUncomment, &QAction::triggered, qtSLiMAppDelegate, &QtSLiMAppDelegate::dispatch_commentUncomment);

VERSIONS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ development head (in the master branch):
3333
add a "Use OpenGL for fast display" checkbox in the Preferences panel, making it possible to disable OpenGL when it doesn't work well (#462)
3434
add genomicElementForPosition() and hasGenomicElementForPosition() methods on Chromosome
3535
policy change: genomicElements property is now in sorted order (not in order of declaration)
36+
add a "Copy as HTML" command to copy script with syntax coloring and such
3637

3738

3839
version 4.2.2 (Eidos version 3.2.2):

0 commit comments

Comments
 (0)