[KLF Application][KLF Tools][KLF Backend][KLF Home]
KLatexFormula Project
klflatexedit.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  * file klflatexedit.cpp
3  * This file is part of the KLatexFormula Project.
4  * Copyright (C) 2011 by Philippe Faist
5  * philippe.faist at bluewin.ch
6  * *
7  * This program is free software; you can redistribute it and/or modify *
8  * it under the terms of the GNU General Public License as published by *
9  * the Free Software Foundation; either version 2 of the License, or *
10  * (at your option) any later version. *
11  * *
12  * This program is distributed in the hope that it will be useful, *
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15  * GNU General Public License for more details. *
16  * *
17  * You should have received a copy of the GNU General Public License *
18  * along with this program; if not, write to the *
19  * Free Software Foundation, Inc., *
20  * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
21  ***************************************************************************/
22 /* $Id: klflatexedit.cpp 603 2011-02-26 23:14:55Z phfaist $ */
23 
24 #include <stack>
25 
26 #include <QObject>
27 #include <QWidget>
28 #include <QTextEdit>
29 #include <QTextDocumentFragment>
30 #include <QTextCursor>
31 
32 #include "klfconfig.h"
33 #include "klfmainwin.h"
34 
35 #include "klflatexedit.h"
36 
37 
38 
40  : QTextEdit(parent), mMainWin(NULL), pHeightHintLines(-1)
41 {
42  mSyntaxHighlighter = new KLFLatexSyntaxHighlighter(this, this);
43 
44  connect(this, SIGNAL(cursorPositionChanged()),
45  mSyntaxHighlighter, SLOT(refreshAll()));
46 
47  setContextMenuPolicy(Qt::DefaultContextMenu);
48 }
49 
51 {
52 }
53 
55 {
56  setLatex("");
57  setFocus();
58  mSyntaxHighlighter->resetEditing();
59 }
60 
61 void KLFLatexEdit::setLatex(const QString& latex)
62 {
63  // don't call setPlainText(); we want to preserve undo history
64  QTextCursor cur = textCursor();
65  cur.beginEditBlock();
66  cur.select(QTextCursor::Document);
67  cur.removeSelectedText();
68  cur.insertText(latex);
69  cur.endEditBlock();
70 }
71 
73 {
74  QSize superSizeHint = QTextEdit::sizeHint();
75  if (pHeightHintLines >= 0) {
76  return QSize(superSizeHint.width(), 4 + QFontMetrics(font()).height()*pHeightHintLines);
77  }
78  return superSizeHint;
79 }
80 
82 {
83  pHeightHintLines = lines;
84  updateGeometry();
85 }
86 
87 
89 {
90  QPoint pos = event->pos();
91 
92  if ( ! textCursor().hasSelection() ) {
93  // move cursor at that point, but not if we have a selection
95  }
96 
97  QMenu * menu = createStandardContextMenu(mapToGlobal(pos));
98 
99  menu->addSeparator();
100 
104  static const struct { const char * instext; int charsback; const char * iconsymb; } delimList[] = {
105  { "\\frac{}{}", 3, "\\frac{a}{b}" },
106  { "\\sqrt{}", 1, "\\sqrt{xyz}" },
107  { "\\sqrt[]{}", 3, "\\sqrt[n]{xyz}" },
108  { "\\textrm{}", 1, "\\textrm{A}" },
109  { "\\textit{}", 1, "\\textit{A}" },
110  { "\\textsl{}", 1, "\\textsl{A}" },
111  { "\\textbf{}", 1, "\\textbf{A}" },
112  { "\\mathrm{}", 1, "\\mathrm{A}" },
113  { "\\mathit{}", 1, "\\mathit{A}" },
114  { NULL, 0, NULL }
115  };
116 
117  QMenu *delimmenu = new QMenu(menu);
118  int k;
119  for (k = 0; delimList[k].instext != NULL; ++k) {
120  QAction *a = new QAction(delimmenu);
121  a->setText(delimList[k].instext);
122  QVariantMap v;
123  v["delim"] = QVariant::fromValue<QString>(QLatin1String(delimList[k].instext));
124  v["charsBack"] = QVariant::fromValue<int>(delimList[k].charsback);
125  a->setData(QVariant(v));
126  a->setIcon(KLFLatexSymbolsCache::theCache()->findSymbolPixmap(QLatin1String(delimList[k].iconsymb)));
127  delimmenu->addAction(a);
128  connect(a, SIGNAL(triggered()), this, SLOT(slotInsertFromActionSender()));
129  }
130 
131  QAction *delimaction = menu->addAction(tr("Insert Delimiter"));
132  delimaction->setMenu(delimmenu);
133 
134  QList<QAction*> actionList;
135  emit insertContextMenuActions(pos, &actionList);
136 
137  if (actionList.size()) {
138  menu->addSeparator();
139  for (k = 0; k < actionList.size(); ++k) {
140  menu->addAction(actionList[k]);
141  }
142  }
143 
144  menu->popup(mapToGlobal(pos));
145  event->accept();
146 }
147 
148 
150 {
151  klfDbg("formats: "<<data->formats());
152  if (mMainWin != NULL)
153  if (mMainWin->canOpenData(data))
154  return true; // data can be opened by main window
155 
156  // or check if we can insert the data ourselves
158 }
159 
161 {
162  bool openerfound = false;
163  klfDbg("formats: "<<data->formats());
164  if (mMainWin != NULL)
165  if (mMainWin->openData(data, &openerfound))
166  return; // data was opened by main window
167  if (openerfound) {
168  // failed to open data, don't insist.
169  return;
170  }
171 
172  klfDbg("mMainWin="<<mMainWin<<" did not handle the paste, doing it ourselves.") ;
173 
174  // insert the data ourselves
176 }
177 
178 void KLFLatexEdit::insertDelimiter(const QString& delim, int charsBack)
179 {
180  QTextCursor c1 = textCursor();
181  c1.beginEditBlock();
182  QString selected = c1.selection().toPlainText();
183  QString toinsert = delim;
184  if (selected.length())
185  toinsert.insert(toinsert.length()-charsBack, selected);
186  c1.removeSelectedText();
187  c1.insertText(toinsert);
188  c1.endEditBlock();
189 
190  if (selected.isEmpty())
191  c1.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, charsBack);
192 
193  setTextCursor(c1);
194 
195  setFocus();
196 }
197 
198 
199 void KLFLatexEdit::slotInsertFromActionSender()
200 {
201  QObject *obj = sender();
202  if (obj == NULL || !obj->inherits("QAction")) {
203  qWarning()<<KLF_FUNC_NAME<<": sender object is not a QAction: "<<obj;
204  return;
205  }
206  QVariant v = qobject_cast<QAction*>(obj)->data();
207  QVariantMap vdata = v.toMap();
208  insertDelimiter(vdata["delim"].toString(), vdata["charsBack"].toInt());
209 }
210 
211 
212 // ------------------------------------
213 
214 
216  : QSyntaxHighlighter(parent) , _textedit(textedit)
217 {
218  setDocument(textedit->document());
219 
220  _caretpos = 0;
221 }
222 
224 {
225 }
226 
228 {
229  _caretpos = position;
230 }
231 
233 {
234  rehighlight();
235 }
236 
237 void KLFLatexSyntaxHighlighter::parseEverything()
238 {
239  QString text;
240  int i = 0;
241  int blockpos;
242  QList<uint> blocklens; // the length of each block
243  std::stack<ParenItem> parens; // the parens that we'll meet
244 
245  QTextBlock block = document()->firstBlock();
246 
247  _rulestoapply.clear();
248  int k;
249  while (block.isValid()) {
250  text = block.text();
251  i = 0;
252  blockpos = block.position();
253  blocklens.append(block.length());
254 
255  while (text.length() < block.length()) {
256  text += "\n";
257  }
258 
259  static QRegExp bsleft("^\\\\left(?!\\w)");
260  static QRegExp bsright("^\\\\right(?!\\w)");
261 
262  i = 0;
263  while ( i < text.length() ) {
264  if (text[i] == '%') {
265  k = 0;
266  while (i+k < text.length() && text[i+k] != '\n')
267  ++k;
268  _rulestoapply.append(FormatRule(blockpos+i, k, FComment));
269  i += k + 1;
270  continue;
271  }
272  if (bsleft.indexIn(text.mid(i)) != -1 ||
273  text[i] == '{' || text[i] == '(' || text[i] == '[') {
274  bool l = (text.mid(i, 5) == "\\left");
275  if (l)
276  i += 5;
277  if (i == text.length()) // ignore a \left with no following character
278  continue;
279  if (text.mid(i,2) == "\\{")
280  ++i; // focus on the '{' sign, not the \\ sign
281  parens.push(ParenItem(blockpos+i, (_caretpos == blockpos+i), text[i].toAscii(), l));
282  if (i > 0 && text[i-1] == '\\') {
283  --i; // allow the next-next if-block for keywords to highlight this "\\{"
284  }
285  }
286  if (bsright.indexIn(text.mid(i)) != -1 || text[i] == '}' || text[i] == ')' || text[i] == ']') {
287  ParenItem p;
288  if (!parens.empty()) {
289  p = parens.top();
290  parens.pop();
291  } else {
292  p = ParenItem(0, false, '!'); // simulate an item
294  _rulestoapply.append(FormatRule(blockpos+i, 1, FLonelyParen));
295  }
296  Format col = FParenMatch;
297  bool l = ( text.mid(i, 6) == "\\right" );
298  if (l)
299  i += 6;
300  if (i == text.length()) // ignore a \right with no following character
301  continue;
302  if (text.mid(i,2) == "\\}")
303  ++i; // focus on the '}' sign, not the \\ sign
304  if ( (l && text[i] == '.' && p.left) || (l && p.ch == '.' && p.left) ) {
305  // special case with \left( blablabla \right. or \left. blablabla \right)
306  col = FParenMatch;
307  } else if ((text[i] == '}' && p.ch != '{') ||
308  (text[i] == ')' && p.ch != '(') ||
309  (text[i] == ']' && p.ch != '[') ||
310  (l != p.left) ) {
311  col = FParenMismatch;
312  }
313  // does this rule span multiple paragraphs, and do we need to show it (eg. cursor right after paren)
314  if (p.highlight || (_caretpos == blockpos+i+1)) {
316  _rulestoapply.append(FormatRule(p.pos, blockpos+i+1-p.pos, col, true));
317  } else {
318  if (p.ch != '!') // simulated item for first pos
319  _rulestoapply.append(FormatRule(p.pos, 1, col));
320  _rulestoapply.append(FormatRule(blockpos+i, 1, col, true));
321  }
322  }
323  if (i > 0 && text[i-1] == '\\') {
324  --i; // allow the next if-block for keywords to highlight this "\\}"
325  }
326  }
327 
328  if (text[i] == '\\') { // a keyword ("\symbol")
329  ++i;
330  k = 0;
331  if (i >= text.length())
332  continue;
333  while (i+k < text.length() && ( (text[i+k] >= 'a' && text[i+k] <= 'z') ||
334  (text[i+k] >= 'A' && text[i+k] <= 'Z') ))
335  ++k;
336  if (k == 0 && i+1 < text.length())
337  k = 1;
338  _rulestoapply.append(FormatRule(blockpos+i-1, k+1, FKeyWord));
339  QString symbol = text.mid(i-1,k+1); // from i-1, length k+1
340  if (symbol.size() > 1) { // no empty backslash
341  klfDbg("symbol="<<symbol<<" i="<<i<<" k="<<k<<" caretpos="<<_caretpos<<" blockpos="<<blockpos);
342  if ( (_caretpos < blockpos+i ||_caretpos >= blockpos+i+k+1) &&
343  !pTypedSymbols.contains(symbol)) { // not typing symbol
344  klfDbg("newSymbolTyped() about to be emitted for : "<<symbol);
345  emit newSymbolTyped(symbol);
346  pTypedSymbols.append(symbol);
347  }
348  }
349  i += k;
350  continue;
351  }
352 
353  ++i;
354  }
355 
356  block = block.next();
357  }
358 
359  QTextBlock lastblock = document()->lastBlock();
360 
361  while ( ! parens.empty() ) {
362  // for each unclosed paren left
363  ParenItem p = parens.top();
364  parens.pop();
365  if (_caretpos == p.pos) {
367  _rulestoapply.append(FormatRule(p.pos, 1, FParenMismatch, true));
368  else
369  _rulestoapply.append(FormatRule(p.pos, lastblock.position()+lastblock.length()-p.pos,
370  FParenMismatch, true));
371  } else { // not on caret positions
373  _rulestoapply.append(FormatRule(p.pos, 1, FLonelyParen));
374  }
375  }
376 }
377 
378 QTextCharFormat KLFLatexSyntaxHighlighter::charfmtForFormat(Format f)
379 {
380  QTextCharFormat fmt;
381  switch (f) {
382  case FNormal:
383  fmt = QTextCharFormat();
384  break;
385  case FKeyWord:
387  break;
388  case FComment:
390  break;
391  case FParenMatch:
393  break;
394  case FParenMismatch:
396  break;
397  case FLonelyParen:
399  break;
400  default:
401  fmt = QTextCharFormat();
402  break;
403  };
404  return fmt;
405 }
406 
407 
409 {
410  klfDbg("text is "<<text);
411 
413  return; // forget everything about synt highlight if we don't want it.
414 
415  QTextBlock block = currentBlock();
416 
417  // printf("\t -- block/position=%d\n", block.position());
418 
419  if (block.position() == 0) {
420  setCaretPos(_textedit->textCursor().position());
421  parseEverything();
422  }
423 
424  QList<FormatRule> blockfmtrules;
425  QVector<QTextCharFormat> charformats;
426 
427  charformats.resize(text.length());
428 
429  blockfmtrules.append(FormatRule(0, text.length(), FNormal));
430 
431  int k, j;
432  for (k = 0; k < _rulestoapply.size(); ++k) {
433  int start = _rulestoapply[k].pos - block.position();
434  int len = _rulestoapply[k].len;
435 
436  if (start < 0) {
437  len += start; // +, start being negative
438  start = 0;
439  }
440  if (start > text.length())
441  continue;
442  if (len > text.length() - start)
443  len = text.length() - start;
444  // apply rule
445  blockfmtrules.append(FormatRule(start, len, _rulestoapply[k].format, _rulestoapply[k].onlyIfFocus));
446  }
447 
448  bool hasfocus = _textedit->hasFocus();
449  for (k = 0; k < blockfmtrules.size(); ++k) {
450  for (j = blockfmtrules[k].pos; j < blockfmtrules[k].end(); ++j) {
451  if ( ! blockfmtrules[k].onlyIfFocus || hasfocus )
452  charformats[j].merge(charfmtForFormat(blockfmtrules[k].format));
453  }
454  }
455  for (j = 0; j < charformats.size(); ++j) {
456  setFormat(j, 1, charformats[j]);
457  }
458 
459  return;
460 }
461 
462 
464 {
465  pTypedSymbols = QStringList();
466 }
void insertDelimiter(const QString &delim, int charsBack=1)
QTextCharFormat fmtComment
Definition: klfconfig.h:211
KLFLatexEdit(QWidget *mainwin)
virtual bool canInsertFromMimeData(const QMimeData *source) const
cursorPositionChanged()
createStandardContextMenu()
cursorForPosition(const QPoint &pos)
static KLFLatexSymbolsCache * theCache()
unsigned int configFlags
Definition: klfconfig.h:209
KLFConfig klfconfig
Definition: klfconfig.cpp:88
bool openData(const QMimeData *mimeData, bool *openerFound=NULL)
canInsertFromMimeData(const QMimeData *source)
contains(const QString &str, Qt::CaseSensitivity cs=Qt::CaseSensitive)
#define klfDbg(streamableItems)
setMenu(QMenu *menu)
setFormat(int start, int count, const QTextCharFormat &format)
addAction(const QString &text)
movePosition(MoveOperation operation, MoveMode mode=MoveAnchor, int n=1)
select(SelectionType selection)
KLFLatexSyntaxHighlighter(QTextEdit *textedit, QObject *parent)
QTextCharFormat fmtKeyword
Definition: klfconfig.h:210
indexIn(const QString &str, int offset=0, CaretMode caretMode=CaretAtZero)
append(const T &value)
QTextCharFormat fmtParenMismatch
Definition: klfconfig.h:213
resize(int size)
popup(const QPoint &p, QAction *atAction=0)
inherits(const char *className)
virtual QSize sizeHint() const
insertText(const QString &text)
QTextCharFormat fmtParenMatch
Definition: klfconfig.h:212
addSeparator()
bool canOpenData(const QByteArray &data)
void insertContextMenuActions(const QPoint &pos, QList< QAction * > *actionList)
setTextCursor(const QTextCursor &cursor)
setData(const QVariant &userData)
#define KLF_FUNC_NAME
insertFromMimeData(const QMimeData *source)
struct KLFConfig::@2 SyntaxHighlighter
void clearLatex()
format(int position)
virtual void insertFromMimeData(const QMimeData *source)
mid(int position, int n=-1)
void setLatex(const QString &latex)
left(int n)
void newSymbolTyped(const QString &symbolName)
setDocument(QTextDocument *doc)
void setCaretPos(int position)
virtual void contextMenuEvent(QContextMenuEvent *event)
insert(int position, const QString &str)
QTextCharFormat fmtLonelyParen
Definition: klfconfig.h:214
virtual ~KLFLatexEdit()
virtual void highlightBlock(const QString &text)
void setHeightHintLines(int lines)

Generated by doxygen 1.8.11