1 /**************************************************************************
2 ** This file is part of LiteIDE
3 **
4 ** Copyright (c) 2011-2019 LiteIDE. All rights reserved.
5 **
6 ** This library is free software; you can redistribute it and/or
7 ** modify it under the terms of the GNU Lesser General Public
8 ** License as published by the Free Software Foundation; either
9 ** version 2.1 of the License, or (at your option) any later version.
10 **
11 ** This library is distributed in the hope that it will be useful,
12 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
13 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 ** Lesser General Public License for more details.
15 **
16 ** In addition, as a special exception, that plugins developed for LiteIDE,
17 ** are allowed to remain closed sourced and can be distributed under any license .
18 ** These rights are included in the file LGPL_EXCEPTION.txt in this package.
19 **
20 **************************************************************************/
21 // Module: markdownedit.cpp
22 // Creator: visualfc <visualfc@gmail.com>
23
24 #include "markdownedit.h"
25 #include "liteeditorapi/liteeditorapi.h"
26 #include <QMenu>
27 #include <QAction>
28 #include <QToolBar>
29 #include <QTextCursor>
30 #include <QTextBlock>
31 #include <QPlainTextEdit>
32 #include <QTextDocument>
33 #include <QRegExp>
34 #include <QDebug>
35 //lite_memory_check_begin
36 #if defined(WIN32) && defined(_MSC_VER) && defined(_DEBUG)
37 #define _CRTDBG_MAP_ALLOC
38 #include <stdlib.h>
39 #include <crtdbg.h>
40 #define DEBUG_NEW new( _NORMAL_BLOCK, __FILE__, __LINE__ )
41 #define new DEBUG_NEW
42 #endif
43 //lite_memory_check_end
44
45 //class Separator : public QAction
46 //{
47 //public:
48 // Separator(QObject *parent) :
49 // QAction(parent)
50 // {
51 // setSeparator(true);
52 // }
53 //};
54
55 //static void updateToolTip(QToolBar *toolBar)
56 //{
57 // foreach(QAction *act, toolBar->actions()) {
58 // if (act->isSeparator()) {
59 // continue;
60 // }
61 // if (act->toolTip() == act->text() && !act->shortcut().isEmpty()) {
62 // act->setToolTip(QString("%1 (%2)").arg(act->text()).arg(act->shortcut().toString()));
63 // }
64 // }
65 //}
66
MarkdownEdit(LiteApi::IApplication * app,LiteApi::IEditor * editor,QObject * parent)67 MarkdownEdit::MarkdownEdit(LiteApi::IApplication *app, LiteApi::IEditor *editor, QObject *parent) :
68 QObject(parent), m_liteApp(app)
69 {
70 m_editor = LiteApi::getTextEditor(editor);
71 if (!m_editor) {
72 return;
73 }
74 m_ed = LiteApi::getPlainTextEdit(editor);
75 if (!m_ed) {
76 return;
77 }
78
79 m_editor->setLineWrap(true);
80
81 LiteApi::IActionContext *actionContext = m_liteApp->actionManager()->getActionContext(this,"Markdown");
82
83 QAction *h1 = new QAction(QIcon("icon:markdown/images/h1.png"),tr("Header (h1)"),this);
84 actionContext->regAction(h1,"Header1","Ctrl+1");
85
86 QAction *h2 = new QAction(QIcon("icon:markdown/images/h2.png"),tr("Header (h2)"),this);
87 actionContext->regAction(h2,"Header2","Ctrl+2");
88
89 QAction *h3 = new QAction(QIcon("icon:markdown/images/h3.png"),tr("Header (h3)"),this);
90 actionContext->regAction(h3,"Header3","Ctrl+3");
91
92 QAction *h4 = new QAction(QIcon("icon:markdown/images/h4.png"),tr("Header (h4)"),this);
93 actionContext->regAction(h4,"Header4","Ctrl+4");
94
95 QAction *h5 = new QAction(QIcon("icon:markdown/images/h5.png"),tr("Header (h5)"),this);
96 actionContext->regAction(h5,"Header5","Ctrl+5");
97
98 QAction *h6 = new QAction(QIcon("icon:markdown/images/h6.png"),tr("Header (h6)"),this);
99 actionContext->regAction(h6,"Header6","Ctrl+6");
100
101 QAction *bold = new QAction(QIcon("icon:markdown/images/bold.png"),tr("Bold"),this);
102 actionContext->regAction(bold,"Bold",QKeySequence::Bold);
103
104 QAction *italic = new QAction(QIcon("icon:markdown/images/italic.png"),tr("Italic"),this);
105 actionContext->regAction(italic,"Italic",QKeySequence::Italic);
106
107 QAction *code = new QAction(QIcon("icon:markdown/images/code.png"),tr("Inline Code"),this);
108 actionContext->regAction(code,"InlineCode","Ctrl+K");
109
110 QAction *link = new QAction(QIcon("icon:markdown/images/link.png"),tr("Link"),this);
111 actionContext->regAction(link,"Link","Ctrl+Shift+L");
112
113 QAction *image = new QAction(QIcon("icon:markdown/images/image.png"),tr("Image"),this);
114 actionContext->regAction(image,"Image","Ctrl+Shift+I");
115
116 QAction *ul = new QAction(QIcon("icon:markdown/images/ul.png"),tr("Unordered List"),this);
117 actionContext->regAction(ul,"UnorderedList","Ctrl+Shift+U");
118
119 QAction *ol = new QAction(QIcon("icon:markdown/images/ol.png"),tr("Ordered List"),this);
120 actionContext->regAction(ol,"OrderedList","Ctrl+Shift+O");
121
122 QAction *bq = new QAction(QIcon("icon:markdown/images/quote.png"),tr("Blockquote"),this);
123 actionContext->regAction(bq,"Blockquote","Ctrl+Shift+Q");
124
125 QAction *hr = new QAction(QIcon("icon:markdown/images/hr.png"),tr("Horizontal Rule"),this);
126 actionContext->regAction(hr,"HorizontalRule","Ctrl+Shift+H");
127
128 QMenu *menu = LiteApi::getEditMenu(editor);
129 if (menu) {
130 menu->addSeparator();
131 QMenu *h = menu->addMenu(tr("Heading"));
132 h->addAction(h1);
133 h->addAction(h2);
134 h->addAction(h3);
135 h->addAction(h4);
136 h->addAction(h5);
137 h->addAction(h6);
138 menu->addSeparator();
139 menu->addAction(link);
140 menu->addAction(image);
141 menu->addSeparator();
142 menu->addAction(bold);
143 menu->addAction(italic);
144 menu->addAction(code);
145 menu->addSeparator();
146 menu->addAction(ul);
147 menu->addAction(ol);
148 menu->addSeparator();
149 menu->addAction(bq);
150 menu->addAction(hr);
151 }
152 menu = LiteApi::getContextMenu(editor);
153 if (menu) {
154 QMenu *h = menu->addMenu(tr("Heading"));
155 h->addAction(h1);
156 h->addAction(h2);
157 h->addAction(h3);
158 h->addAction(h4);
159 h->addAction(h5);
160 h->addAction(h6);
161 menu->addSeparator();
162 menu->addAction(link);
163 menu->addAction(image);
164 menu->addSeparator();
165 menu->addAction(bold);
166 menu->addAction(italic);
167 menu->addAction(code);
168 menu->addSeparator();
169 menu->addAction(ul);
170 menu->addAction(ol);
171 menu->addSeparator();
172 menu->addAction(bq);
173 menu->addAction(hr);
174 }
175
176 QToolBar *toolBar = LiteApi::getEditToolBar(editor);
177 if (toolBar) {
178 toolBar->addSeparator();
179 toolBar->addAction(h1);
180 toolBar->addAction(h2);
181 toolBar->addAction(h3);
182 toolBar->addSeparator();
183 toolBar->addAction(link);
184 toolBar->addAction(image);
185 toolBar->addSeparator();
186 toolBar->addAction(bold);
187 toolBar->addAction(italic);
188 toolBar->addAction(code);
189 toolBar->addSeparator();
190 toolBar->addAction(ul);
191 toolBar->addAction(ol);
192 toolBar->addSeparator();
193 toolBar->addAction(bq);
194 toolBar->addAction(hr);
195 //updateToolTip(toolBar);
196 }
197
198 connect(editor,SIGNAL(destroyed()),this,SLOT(deleteLater()));
199 connect(h1,SIGNAL(triggered()),this,SLOT(h1()));
200 connect(h2,SIGNAL(triggered()),this,SLOT(h2()));
201 connect(h3,SIGNAL(triggered()),this,SLOT(h3()));
202 connect(h4,SIGNAL(triggered()),this,SLOT(h4()));
203 connect(h5,SIGNAL(triggered()),this,SLOT(h5()));
204 connect(h6,SIGNAL(triggered()),this,SLOT(h6()));
205 connect(bold,SIGNAL(triggered()),this,SLOT(bold()));
206 connect(italic,SIGNAL(triggered()),this,SLOT(italic()));
207 connect(code,SIGNAL(triggered()),this,SLOT(code()));
208 connect(link,SIGNAL(triggered()),this,SLOT(link()));
209 connect(image,SIGNAL(triggered()),this,SLOT(image()));
210 connect(ul,SIGNAL(triggered()),this,SLOT(ul()));
211 connect(ol,SIGNAL(triggered()),this,SLOT(ol()));
212 connect(bq,SIGNAL(triggered()),this,SLOT(bq()));
213 connect(hr,SIGNAL(triggered()),this,SLOT(hr()));
214
215 //m_ed->installEventFilter(this);
216 }
217
~MarkdownEdit()218 MarkdownEdit::~MarkdownEdit()
219 {
220 }
221
222
223
insert_head(const QString & tag,bool blockStart)224 void MarkdownEdit::insert_head(const QString &tag, bool blockStart)
225 {
226 QTextCursor cur = m_ed->textCursor();
227 cur.beginEditBlock();
228 if (cur.hasSelection()) {
229 QTextBlock begin = m_ed->document()->findBlock(cur.selectionStart());
230 QTextBlock end = m_ed->document()->findBlock(cur.selectionEnd());
231 if (end.position() == cur.selectionEnd()) {
232 end = end.previous();
233 }
234 QTextBlock block = begin;
235 do {
236 if (block.text().length() > 0) {
237 if (blockStart) {
238 cur.setPosition(block.position());
239 } else {
240 QString text = block.text();
241 foreach(QChar c, text) {
242 if (!c.isSpace()) {
243 cur.setPosition(block.position()+text.indexOf(c));
244 break;
245 }
246 }
247 }
248 cur.insertText(tag);
249 }
250 block = block.next();
251 } while(block.isValid() && block.position() <= end.position());
252 } else {
253 if (blockStart) {
254 cur.setPosition(cur.block().position());
255 } else {
256 QTextBlock block = cur.block();
257 QString text = block.text();
258 foreach(QChar c, text) {
259 if (!c.isSpace()) {
260 cur.setPosition(block.position()+text.indexOf(c));
261 break;
262 }
263 }
264 }
265 cur.insertText(tag);
266 }
267 cur.endEditBlock();
268 m_ed->setTextCursor(cur);
269 }
270
mark_selection(const QString & mark)271 void MarkdownEdit::mark_selection(const QString &mark)
272 {
273 mark_selection(mark,mark);
274 }
275
mark_selection(const QString & mark1,const QString & mark2)276 void MarkdownEdit::mark_selection(const QString &mark1, const QString &mark2)
277 {
278 QTextCursor cur = m_ed->textCursor();
279 cur.beginEditBlock();
280 if (cur.hasSelection()) {
281 QTextBlock begin = m_ed->document()->findBlock(cur.selectionStart());
282 QTextBlock end = m_ed->document()->findBlock(cur.selectionEnd());
283 if (end.position() == cur.selectionEnd()) {
284 end = end.previous();
285 }
286 int n1 = cur.selectionStart();
287 int n2 = cur.selectionEnd();
288 QTextBlock block = begin;
289 do {
290 int c1 = block.position();
291 int c2 = c1+block.text().length();
292 if (block.position() == begin.position() && c1 < n1) {
293 c1 = n1;
294 }
295 if (c2 > n2) {
296 c2 = n2;
297 }
298 if (c2 > c1) {
299 if (!mark1.isEmpty()) {
300 cur.setPosition(c1);
301 cur.insertText(mark1);
302 n2 += mark1.length();
303 }
304 if (!mark2.isEmpty()) {
305 cur.setPosition(c2+mark1.length());
306 cur.insertText(mark2);
307 n2 += mark2.length();
308 }
309 }
310 block = block.next();
311 } while(block.isValid() && block.position() <= end.position());
312 } else {
313 int pos = cur.position();
314 cur.insertText(mark1+mark2);
315 cur.setPosition(pos+mark1.length());
316 }
317 cur.endEditBlock();
318 m_ed->setTextCursor(cur);
319 }
320
h1()321 void MarkdownEdit::h1()
322 {
323 insert_head("# ");
324 }
325
h2()326 void MarkdownEdit::h2()
327 {
328 insert_head("## ");
329 }
330
h3()331 void MarkdownEdit::h3()
332 {
333 insert_head("### ");
334 }
335
h4()336 void MarkdownEdit::h4()
337 {
338 insert_head("#### ");
339 }
340
h5()341 void MarkdownEdit::h5()
342 {
343 insert_head("#####");
344 }
345
h6()346 void MarkdownEdit::h6()
347 {
348 insert_head("###### ");
349 }
350
bold()351 void MarkdownEdit::bold()
352 {
353 mark_selection("**");
354 }
355
italic()356 void MarkdownEdit::italic()
357 {
358 mark_selection("_");
359 }
360
code()361 void MarkdownEdit::code()
362 {
363 mark_selection("`");
364 }
365
link()366 void MarkdownEdit::link()
367 {
368 QTextCursor cursor = m_ed->textCursor();
369 cursor.beginEditBlock();
370 if (cursor.hasSelection()) {
371 int n1 = cursor.selectionStart();
372 int n2 = cursor.selectionEnd();
373 cursor.setPosition(n1);
374 cursor.insertText("[");
375 cursor.setPosition(n2+1);
376 cursor.insertText("]()");
377 cursor.setPosition(n2+3);
378 } else {
379 int n = cursor.position();
380 cursor.insertText("[]()");
381 cursor.setPosition(n+1);
382 }
383 cursor.endEditBlock();
384 m_ed->setTextCursor(cursor);
385 }
386
image()387 void MarkdownEdit::image()
388 {
389 QTextCursor cursor = m_ed->textCursor();
390 cursor.beginEditBlock();
391 if (cursor.hasSelection()) {
392 int n1 = cursor.selectionStart();
393 int n2 = cursor.selectionEnd();
394 cursor.setPosition(n1);
395 cursor.insertText("![");
396 cursor.setPosition(n2+2);
397 cursor.insertText("]()");
398 cursor.setPosition(n2+4);
399 } else {
400 int n = cursor.position();
401 cursor.insertText("![]()");
402 cursor.setPosition(n+2);
403 }
404 cursor.endEditBlock();
405 m_ed->setTextCursor(cursor);
406 }
407
ul()408 void MarkdownEdit::ul()
409 {
410 insert_head("* ",false);
411 }
412
ol()413 void MarkdownEdit::ol()
414 {
415 insert_head("1. ",false);
416 }
417
bq()418 void MarkdownEdit::bq()
419 {
420 insert_head("> ");
421 }
422
hr()423 void MarkdownEdit::hr()
424 {
425 QTextCursor cursor = m_ed->textCursor();
426 if (cursor.hasSelection()) {
427 cursor.setPosition(cursor.selectionEnd());
428 }
429 cursor.insertText("\n***\n");
430 m_ed->setTextCursor(cursor);
431 }
432
gotoLine(int line,int column)433 void MarkdownEdit::gotoLine(int line, int column)
434 {
435 const int blockNumber = line;
436 const QTextBlock &block = m_ed->document()->findBlockByLineNumber(blockNumber);
437 if (block.isValid()) {
438 QTextCursor cursor(block);
439 if (column > 0) {
440 cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, column);
441 } else {
442 int pos = cursor.position();
443 while (m_ed->document()->characterAt(pos).category() == QChar::Separator_Space) {
444 ++pos;
445 }
446 cursor.setPosition(pos);
447 }
448 m_ed->setTextCursor(cursor);
449 m_ed->ensureCursorVisible();
450 }
451 }
452
eventFilter(QObject * obj,QEvent * event)453 bool MarkdownEdit::eventFilter(QObject *obj, QEvent *event)
454 {
455 if (obj == m_ed && event->type() == QEvent::KeyPress) {
456 QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
457 if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) {
458 // QObject::eventFilter(obj, event);
459 // QTextCursor cur = m_ed->textCursor();
460 // QTextBlock prev = cur.block().previous();
461 // if (prev.isValid()) {
462 // QString text = prev.text();
463 // qDebug() << text;
464 // if (text.length() > 2 && text.at(1) == ' ' && QString("*+-").contains(text.at(0))) {
465 // cur.insertText(text.left(2));
466 // }
467 // }
468 // m_ed->setTextCursor(cur);
469 return false;
470 }
471 }
472 return QObject::eventFilter(obj, event);
473 }
474
475