/***************************************************************************
* file klflatexsymbols.cpp
* This file is part of the KLatexFormula Project.
* Copyright (C) 2011 by Philippe Faist
* philippe.faist@bluewin.ch
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
/* $Id$ */
#include <stdio.h>
#include <QFile>
#include <QDir>
#include <QTextStream>
#include <QScrollArea>
#include <QRegExp>
#include <QList>
#include <QStringList>
#include <QProgressDialog>
#include <QGridLayout>
#include <QPushButton>
#include <QHash>
#include <QStackedWidget>
#include <QLineEdit>
#include <QMessageBox>
#include <QScrollBar>
#include <QApplication>
#include <QCloseEvent>
#include <QDebug>
#include <QPainter>
#include <QStyleFactory>
#include <QDomDocument>
#include <QDomElement>
#include <klfbackend.h>
#include <ui_klflatexsymbols.h>
#include <klfpixmapbutton.h>
#include <klfrelativefont.h>
#include "klfmain.h"
#include "klfconfig.h"
#include "klflatexsymbols.h"
#include "klflatexsymbols_p.h"
// ------------------
KLF_EXPORT QDebug operator<<(QDebug str, const KLFLatexSymbol& s)
{
return str << "KLFLatexSymbol["<<s.symbol
<<(s.symbol_option?"[]":"")
<<(s.symbol_numargs>0?qPrintable("{"+QString::number(s.symbol_numargs)+"x}"):"")
<<";preview="<<(s.previewlatex.size()?qPrintable("`"+s.previewlatex+"'"):"")
<<";preamblelist/size="<<s.preamble.size()
<<";txtmd="<<s.textmode<<";hidden="<<s.hidden
<<";kw="<<s.keywords;
}
KLFLatexSymbol::KLFLatexSymbol(const QDomElement& e)
: symbol(), preamble(), textmode(false), bbexpand(), hidden(false)
{
// preamble
QDomNodeList usepackagelist = e.elementsByTagName("usepackage");
int k;
for (k = 0; k < usepackagelist.size(); ++k) {
QString package = usepackagelist.at(k).toElement().attribute("name");
if (package[0] == '[' || package[0] == '{')
preamble.append(QString("\\usepackage%1").arg(package));
else
preamble.append(QString("\\usepackage{%1}").arg(package));
}
QDomNodeList preamblelinelist = e.elementsByTagName("preambleline");
for (k = 0; k < preamblelinelist.size(); ++k) {
preamble.append(preamblelinelist.at(k).toElement().text());
}
// textmode
if (e.attribute("textmode") == "true")
textmode = true;
else
textmode = false;
if (e.elementsByTagName("hidden").size() > 0)
hidden = true;
// bb offset
QDomNodeList bblist = e.elementsByTagName("bb");
if (bblist.size() > 1) {
fprintf(stderr, "WARNING: Expected at most single <bb expand=\"..\"/> item!\n");
}
if (bblist.size()) {
sscanf(bblist.at(0).toElement().attribute("expand").toLatin1().constData(), "%d,%d,%d,%d",
&bbexpand.t, &bbexpand.r, &bbexpand.b, &bbexpand.l);
}
// latex code
QDomNodeList latexlist = e.elementsByTagName("latex");
if (latexlist.size() != 1) {
fprintf(stderr, "WARNING: Expected single <latex>...</latex> in symbol entry!\n");
}
if (latexlist.size() == 0) {
return;
}
QRegExp rx_bool("\\s*(y(?:es)?|t(?:rue)?|on|1)|(n(?:o)?|f(?:alse)?|off|0)\\s*", Qt::CaseInsensitive);
QDomElement latexnode = latexlist.at(0).toElement();
symbol = latexnode.text();
QString symopt = latexnode.attribute("option", "no");
bool boolok = rx_bool.exactMatch(symopt);
if (!boolok) {
qWarning()<<KLF_FUNC_NAME<<": Bad boolean format "<<symopt;
symbol_option = false;
} else {
// if cap(1) is non-empty, TRUE; if cap(2) is non-empty then FALSE
symbol_option = rx_bool.cap(1).size();
}
QString nargs = latexnode.attribute("numargs", "0");
bool uintok = true;
symbol_numargs = nargs.toUInt(&uintok);
if (!uintok) {
qWarning()<<KLF_FUNC_NAME<<": Bad integer format "<<nargs;
}
// latex preview code
QDomNodeList previewlatexlist = e.elementsByTagName("preview-latex");
if (previewlatexlist.size() > 1) {
fprintf(stderr, "WARNING: Expected at most one <preview-latex>...</preview-latex> in symbol entry!\n");
}
if (previewlatexlist.size() > 0) {
QDomElement previewlatexnode = previewlatexlist.at(0).toElement();
previewlatex = previewlatexnode.text();
} else {
previewlatex = QString();
}
// keywords
QDomNodeList keywordslist = e.elementsByTagName("keywords");
for (k = 0; k < keywordslist.size(); ++k) {
QStringList kw;
kw = keywordslist.at(k).toElement().text().split(QRegExp("\\s+"), QString::SkipEmptyParts);
keywords << kw;
}
klfDbg("read symbol "<<symbol<<" hidden="<<hidden);
}
QString KLFLatexSymbol::symbolWithArgs() const
{
QString s = symbol;
if (symbol_option)
s += "[]";
int n = symbol_numargs;
while (n--)
s += "{}";
return s;
}
QString KLFLatexSymbol::latexCodeForPreview() const
{
if (previewlatex.size())
return previewlatex;
static const QString dummyopt = "n";
static const QStringList dummyargs =
QStringList()<<"x"<<"y"<<"z"<<"w"<<"a"<<"b"<<"c"<<"d"<<"e"<<"f";
int k;
QString s = symbol;
if (symbol_option)
s += "["+dummyopt+"]";
for (k = 0; k < symbol_numargs; ++k)
s += "{"+dummyargs[k%dummyargs.size()]+"}";
return s;
}
KLF_EXPORT bool operator==(const KLFLatexSymbol& a, const KLFLatexSymbol& b)
{
return a.symbol == b.symbol &&
a.symbol_numargs == b.symbol_numargs &&
a.symbol_option == b.symbol_option &&
a.previewlatex == b.previewlatex &&
a.textmode == b.textmode &&
a.preamble == b.preamble &&
a.bbexpand.t == b.bbexpand.t &&
a.bbexpand.r == b.bbexpand.r &&
a.bbexpand.b == b.bbexpand.b &&
a.bbexpand.l == b.bbexpand.l &&
a.hidden == b.hidden;
}
QDataStream& operator<<(QDataStream& stream, const KLFLatexSymbol& s)
{
KLF_DEBUG_BLOCK(KLF_FUNC_NAME) ;
return stream << s.symbol
<< (quint8)s.symbol_option
<< (quint8)s.symbol_numargs
<< s.previewlatex
<< s.preamble
<< (quint8)s.textmode
<< (qint16)s.bbexpand.t << (qint16)s.bbexpand.r << (qint16)s.bbexpand.b << (qint16)s.bbexpand.l
<< (quint8)s.hidden
<< s.keywords;
}
QDataStream& operator>>(QDataStream& stream, KLFLatexSymbol& s)
{
quint8 symopt, symnargs, textmode, hidden;
struct { qint16 t, r, b, l; } readbbexpand;
stream >> s.symbol
>> symopt
>> symnargs
>> s.previewlatex
>> s.preamble
>> textmode
>> readbbexpand.t >> readbbexpand.r >> readbbexpand.b >> readbbexpand.l
>> hidden
>> s.keywords;
s.symbol_option = symopt;
s.symbol_numargs = symnargs;
s.bbexpand.t = readbbexpand.t;
s.bbexpand.r = readbbexpand.r;
s.bbexpand.b = readbbexpand.b;
s.bbexpand.l = readbbexpand.l;
s.textmode = textmode;
s.hidden = hidden;
return stream;
}
// -----------------------------------------------------------
KLFLatexSymbolsCache::KLFLatexSymbolsCache(QObject * parent)
: QObject(parent)
{
KLF_DEBUG_TIME_BLOCK(KLF_FUNC_NAME) ;
KLF_INIT_PRIVATE(KLFLatexSymbolsCache) ;
d->device_pixel_ratio = 1.0;
d->flag_modified = false;
}
KLFLatexSymbolsCache::~KLFLatexSymbolsCache()
{
KLF_DELETE_PRIVATE ;
}
int KLFLatexSymbolsCache::loadCacheStream(QDataStream& stream)
{
QString readHeader;
QString readCompatKLFVersion;
bool r = klfDataStreamReadHeader(stream, QStringList()<<"KLATEXFORMULA_SYMBOLS_PIXMAP_CACHE",
&readHeader, &readCompatKLFVersion);
if (!r) {
klfDbg("failed to read symbolscache data header. readHeader="<<readHeader
<<", readcompatklfver="<<readCompatKLFVersion) ;
if (readHeader.isEmpty() || readCompatKLFVersion.isEmpty())
return BadHeader;
// otherwise, it's a bad version error
return BadVersion;
}
// stream is now ready to read
stream >> d->cache;
d->flag_modified = false;
return 0;
}
int KLFLatexSymbolsCache::saveCacheStream(QDataStream& stream)
{
klfDataStreamWriteHeader(stream, "KLATEXFORMULA_SYMBOLS_PIXMAP_CACHE");
// stream is now ready to be written
stream << d->cache;
d->flag_modified = false;
return 0;
}
bool KLFLatexSymbolsCache::cacheNeedsSave() const
{
return d->flag_modified;
}
QPixmap KLFLatexSymbolsCache::getSymbolPixmap(const KLFLatexSymbol& sym)
{
klfDbg("sym.symbol="<<sym.symbol) ;
klfDbg("full symbol: "<<sym) ;
if (d->cache.contains(sym)) {
klfDbg("Found symbol in cache! pixmap is null="<<d->cache[sym].pix.isNull()
<<"; sym.preamble="<<sym.preamble.join(";"));
return QPixmap(d->cache[sym].pix);
}
// if we weren't able to load it from cache, show failed icon
return QPixmap(":/pics/badsym.png");
}
void KLFLatexSymbolsCache::setSymbolPixmap(const KLFLatexSymbol & sym, const QPixmap & pix)
{
klfDbg("sym.symbol="<<sym.symbol) ;
klfDbg("full symbol: "<<sym) ;
{
KLF_DEBUG_TIME_BLOCK(KLF_FUNC_NAME+"/clean cache duplicate test") ;
// clean cache: make sure there are no two duplicate symbols (this is for the popup hint parser,
// so that it doesn't detect old symbols in the cache)
QHash<KLFLatexSymbol,SymbolInfo>::iterator it = d->cache.begin();
while (it != d->cache.end()) {
klfDbg("Testing symbol "<<it.key().symbol<<",preamble="<<it.key().preamble.join(",")
<< "for being a duplicate of "<<sym.symbol);
if (it.key() == sym && !it.value().confirmed) {
klfDbg("erasing duplicate marked unconfirmed.");
it = d->cache.erase(it); // erase old symbol entry
} else {
++it;
}
}
}
d->cache[sym].pix = pix;
d->cache[sym].confirmed = true;
d->flag_modified = true;
}
void KLFLatexSymbolsCache::setSymbolPixmaps(const QHash<KLFLatexSymbol,QPixmap>& sympixmaps)
{
for (QHash<KLFLatexSymbol,QPixmap>::const_iterator it = sympixmaps.begin();
it != sympixmaps.end(); ++it) {
KLFLatexSymbol sym = it.key();
QPixmap pix = it.value();
klfDbg("setting symbol pixmap for " << sym.symbol ) ;
setSymbolPixmap(sym, pix);
}
}
void KLFLatexSymbolsCache::setBackendSettings(const KLFBackend::klfSettings& settings)
{
d->backendsettings = settings;
}
void KLFLatexSymbolsCache::setDevicePixelRatio(qreal ratio)
{
d->device_pixel_ratio = ratio;
}
bool KLFLatexSymbolsCache::contains(const KLFLatexSymbol & sym)
{
return d->cache.contains(sym);
}
KLFLatexSymbol KLFLatexSymbolsCache::findSymbol(const QString& symbolCode)
{
for (QHash<KLFLatexSymbol,SymbolInfo>::const_iterator it = d->cache.begin();
it != d->cache.end(); ++it) {
if (it.key().symbol == symbolCode) {
return it.key();
}
}
return KLFLatexSymbol();
}
QList<KLFLatexSymbol> KLFLatexSymbolsCache::findSymbols(const QString& symbolCode)
{
KLF_DEBUG_BLOCK(KLF_FUNC_NAME) ;
QList<KLFLatexSymbol> slist;
for (QHash<KLFLatexSymbol,SymbolInfo>::const_iterator it = d->cache.begin();
it != d->cache.end(); ++it) {
if (it.key().symbol == symbolCode) {
klfDbg("found symbol: "<<it.key());
slist << it.key();
}
}
return slist;
}
QStringList KLFLatexSymbolsCache::symbolCodeList()
{
QStringList l;
for (QHash<KLFLatexSymbol,SymbolInfo>::const_iterator it = d->cache.begin();
it != d->cache.end(); ++it)
l << it.key().symbol;
return l;
}
QPixmap KLFLatexSymbolsCache::findSymbolPixmap(const QString& symbolCode)
{
KLFLatexSymbol sym = findSymbol(symbolCode);
if (sym.symbol.isEmpty()) {
// invalid symbol
klfWarning("Can't find symbol "<<symbolCode);
return QPixmap();
}
// return the pixmap from cache
return d->cache[sym].pix;
}
void KLFLatexSymbolsCache::debugDump()
{
#ifdef KLF_DEBUG
klfDbg("dumping cache.");
QHash<KLFLatexSymbol,SymbolInfo>::const_iterator it;
int k;
for (it = d->cache.begin(), k = 0; it != d->cache.end(); ++it, ++k) {
const KLFLatexSymbol& s = it.key();
const QPixmap& p = it.value().pix;
qDebug(" #%4d %s (%s) prembl/sz=%d txtmd=%s hid=%s pix/sz=(%d,%d) confirmed=%s", k,
qPrintable(s.symbolWithArgs()), qPrintable(s.previewlatex),
s.preamble.size(), s.textmode?"yes":"no", s.hidden?"yes":"no",
p.width(), p.height(), it.value().confirmed?"yes":"no"
);
}
#endif
}
// private
int KLFLatexSymbolsCache::loadCacheFrom(const QString& fname, int version)
{
QFile f(fname);
if ( ! f.open(QIODevice::ReadOnly) ) {
klfDbg("Failed to open "<<fname) ;
return -1;
}
QDataStream ds(&f);
if (version >= 0) {
ds.setVersion(version);
}
int r = loadCacheStream(ds);
return r;
}
void KLFLatexSymbolsCache::loadKlfCache()
{
// load the klf cache
QStringList cachefiles;
cachefiles
<< klfconfig.homeConfigDir + QLatin1String("/") + d->klf_cache_file_name()
<< ":/data/base_"+d->klf_cache_file_name()
;
int k;
bool ok = false;
for (k = 0; !ok && k < cachefiles.size(); ++k) {
klfDbg("trying to load from "<<cachefiles[k]) ;
ok = ( loadCacheFrom(cachefiles[k], QDataStream::Qt_4_4)
== KLFLatexSymbolsCache::Ok ) ;
if (!ok) {
klfDbg("trying to load from "<<cachefiles[k]<<" with default header datastream version") ;
ok = ( loadCacheFrom(cachefiles[k], -1)
== KLFLatexSymbolsCache::Ok ) ;
}
}
if ( ! ok ) {
klfWarning("Can't find and read cache file!");
}
}
void KLFLatexSymbolsCache::saveToKlfCache()
{
saveCache(klfconfig.homeConfigDir + QLatin1String("/") + d->klf_cache_file_name());
}
void KLFLatexSymbolsCache::saveCache(const QString & fname)
{
KLF_DEBUG_BLOCK(KLF_FUNC_NAME) ;
if (cacheNeedsSave()) {
// QString s = klfconfig.homeConfigDir + relcachefile();
QFile f(fname);
if ( ! f.open(QIODevice::WriteOnly) ) {
klfWarning("Can't save cache to file " << fname);
return;
}
QDataStream ds(&f);
ds.setVersion(QDataStream::Qt_4_4);
saveCacheStream(ds);
klfDbg("Saved cache to file "<<fname);
}
}
// -----------------------------------------------------------
KLFLatexSymbolsView::KLFLatexSymbolsView(const QString& category, QWidget *parent)
: QScrollArea(parent), _category(category)
{
mFrame = new QWidget(this);
setWidgetResizable(true);
// mFrame->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
// mFrame->setFrameShadow(QFrame::Sunken);
// mFrame->setFrameShape(QFrame::Box);
mFrame->setObjectName("frmSymbolList");
// mFrame->setFrameShadow(QFrame::Plain);
// mFrame->setFrameShape(QFrame::NoFrame);
// mFrame->setMidLineWidth(0);
// mFrame->setLineWidth(0);
mLayout = 0;
mSpacerItem = 0;
setWidget(mFrame);
}
void KLFLatexSymbolsView::setSymbolList(const QList<KLFLatexSymbol>& symbols)
{
_symbols.clear();
appendSymbolList(symbols);
}
void KLFLatexSymbolsView::appendSymbolList(const QList<KLFLatexSymbol>& symbols)
{
// filter out hidden symbols
int k;
for (k = 0; k < symbols.size(); ++k)
if ( ! symbols[k].hidden )
_symbols.append(symbols[k]);
}
void KLFLatexSymbolsView::buildDisplay(KLFLatexSymbolsCache * cache)
{
KLF_DEBUG_TIME_BLOCK(KLF_FUNC_NAME) ;
#ifdef KLF_WS_MAC
QStyle *buttonstyle = QStyleFactory::create("fusion");
// QPalette pal = palette();
// pal.setColor(QPalette::Window, QColor(206,207,233));
// pal.setColor(QPalette::Base, QColor(206,207,233));
// pal.setColor(QPalette::Button, QColor(206,207,233));
#endif
mLayout = new QGridLayout(mFrame);
int i, k;
for (i = 0; i < _symbols.size(); ++i) {
QPixmap p = cache->getSymbolPixmap(_symbols[i]);
KLFPixmapButton *btn = new KLFPixmapButton(p, mFrame);
#ifdef KLF_WS_MAC
btn->setStyle(buttonstyle);
// btn->setPalette(pal);
#endif
btn->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred));
btn->setProperty("symbol", QVariant::fromValue<int>(i));
btn->setProperty("gridpos", QPoint(-1,-1));
btn->setProperty("gridcolspan", -1);
btn->setProperty("myWidth", p.width() + 4);
QString symcode = _symbols[i].symbol;
if (_symbols[i].symbol_option) {
symcode += "[]";
}
for (k = 0; _symbols[i].symbol_numargs > 0 && k < _symbols[i].symbol_numargs; ++k) {
symcode += "{}";
}
QString tooltiptext =
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\""
" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" />"
"<style type=\"text/css\">\n"
"p { white-space: pre-wrap; padding: 0px; margin: 0px 0px 2px 0px; }\n"
"pre { padding: 0px; margin: 0px 0px 2px 0px; }\n"
"</style>"
"</head>"
"<body>\n"
"<p style=\"white-space: pre\">"+tr("LaTeX code:")+" <b><tt>"+symcode+"</tt></b>"+
(_symbols[i].textmode?tr(" [in text mode]"):QString(""))+
+"</p>";
if (_symbols[i].preamble.size()) {
tooltiptext += "<p>"+tr("Requires:")+"<b><pre>" +
_symbols[i].preamble.join("\n")+"</pre></b></p>";
}
if (_symbols[i].keywords.size()) {
tooltiptext += "<p>"+tr("Key Words:")+"<pre><b>" +
_symbols[i].keywords.join("</b>, <b>")+"</b></pre></p>";
}
tooltiptext += "</body></html>";
btn->setToolTip(tooltiptext);
//klfDbg("tooltip text is "<<tooltiptext);
connect(btn, SIGNAL(clicked()), this, SLOT(slotSymbolActivated()));
mSymbols.append(btn);
}
mSpacerItem = new QSpacerItem(1, 1, QSizePolicy::Fixed, QSizePolicy::Expanding);
recalcLayout();
}
void KLFLatexSymbolsView::recalcLayout()
{
int row = 0, col = 0, colspan;
int n = klfconfig.UI.symbolsPerLine;
int quantawidth = 55; // hard-coded here
// printf("DEBUG: n=%d, quantawidth=%d\n", n, quantawidth);
int i;
// now add them all again as needed
for (i = 0; i < mSymbols.size(); ++i) {
colspan = 1 + mSymbols[i]->property("myWidth").toInt() / quantawidth;
if (colspan < 1)
colspan = 1;
if (colspan > n)
colspan = n;
if (col + colspan > n) {
row++;
col = 0;
}
if (mSymbols[i]->property("gridpos") != QPoint(row, col) ||
mSymbols[i]->property("gridcolspan") != colspan) {
// printf("DEBUG: %d: setting to (%d,%d)+(1,%d)\n", i, row, col, colspan);
mSymbols[i]->setProperty("gridpos", QPoint(row, col));
mSymbols[i]->setProperty("gridcolspan", colspan);
mLayout->removeWidget(mSymbols[i]);
mLayout->addWidget(mSymbols[i], row, col, 1, colspan);
}
col += colspan;
if (col >= n) {
row++;
col = 0;
}
}
// remove spacer and add it again
mLayout->removeItem(mSpacerItem);
mLayout->addItem(mSpacerItem, row+1, 0);
setMinimumWidth(mFrame->sizeHint().width() + verticalScrollBar()->width() + 2);
}
void KLFLatexSymbolsView::slotSymbolActivated()
{
QObject *s = sender();
int i = s->property("symbol").toInt();
if (i < 0 || i >= _symbols.size())
qWarning()<<KLF_FUNC_NAME<<": Inavlid symbol index "<<i;
else
emit symbolActivated(_symbols[i]);
}
static bool stringlist_contains(const QStringList& l, const QString& qs, Qt::CaseSensitivity cs)
{
QRegExp qsrx(".*"+QRegExp::escape(qs)+".*", cs);
return l.indexOf(qsrx) >= 0;
}
bool KLFLatexSymbolsView::symbolMatches(int symbol, const QString& curqstr)
{
if (symbol < 0 || symbol >= mSymbols.size())
return false;
int symIndex = mSymbols[symbol]->property("symbol").toInt();
if (symIndex < 0 || symIndex >= _symbols.size()) {
qWarning()<<KLF_FUNC_NAME<<": Inavlid symbol index "<<symIndex;
return false;
}
// (X)Emacs-style: presence of capital letter triggers case sensitive search
Qt::CaseSensitivity cs = (curqstr.contains(QRegExp("[A-Z]")) ? Qt::CaseSensitive : Qt::CaseInsensitive) ;
if ( _symbols[symIndex].symbol.contains(curqstr, cs) ||
stringlist_contains(_symbols[symIndex].preamble, curqstr, cs) ||
stringlist_contains(_symbols[symIndex].keywords, curqstr, cs) ) {
klfDbg("found match at "<<symIndex<<": "<<_symbols[symIndex].symbol) ;
return true;
}
return false;
}
void KLFLatexSymbolsView::highlightSearchMatches(int currentMatch, const QString& curqstr)
{
QString stylesheets[] = {
// don't affect tooltips: give KLFPixmapButton { } scopes
"",
"KLFPixmapButton { background-color: rgb(180,180,255); }",
"KLFPixmapButton { background-color: rgb(0,0,255); }"
};
if (currentMatch == -1) {
// abort search
stylesheets[0] = stylesheets[1] = stylesheets[2] = QString();
}
int k;
for (k = 0; k < mSymbols.size(); ++k) {
int which = 0;
if (k == currentMatch)
which = 2;
else if (curqstr.size() && symbolMatches(k, curqstr))
which = 1;
mSymbols[k]->setStyleSheet(stylesheets[which]);
}
}
KLFLatexSymbols::KLFLatexSymbols(QWidget *parent, const KLFBackend::klfSettings& baseSettings)
: QWidget(
#if defined(KLF_WS_WIN) || defined(KLF_WS_MAC)
0 /* parent */
#else
parent /* 0 */
#endif
,
#if defined(KLF_WS_MAC)
Qt::Tool /* to forbid windows from tabbing together */
#else
/*Qt::Tool*/ Qt::Window /*0*/
#endif
)
{
KLF_DEBUG_TIME_BLOCK(KLF_FUNC_NAME) ;
Q_UNUSED(parent) ;
// set up private stuff
KLF_INIT_PRIVATE(KLFLatexSymbols) ;
// set up UI
u = new Ui::KLFLatexSymbols;
u->setupUi(this);
setAttribute(Qt::WA_StyledBackground);
qreal device_pixel_ratio = devicePixelRatio();
klfDbg("device pixel ratio = " << device_pixel_ratio) ;
// create and set up the cache
d->pCache = new KLFLatexSymbolsCache(this);
d->pCache->setDevicePixelRatio( device_pixel_ratio );
d->pCache->loadKlfCache();
// set up the preview generator
d->pPreviewsGenerator = new KLFLatexSymbolPreviewsGenerator; // leave w/o parent as in basic QThread example
d->pPreviewsGenerator->setBackendSettings(baseSettings);
d->pPreviewsGenerator->setDevicePixelRatio( device_pixel_ratio );
d->pPreviewsGenerator->moveToThread(& d->workerThread);
connect(d->pPreviewsGenerator, SIGNAL(started()), u->wGenPreviews, SLOT(show()));
connect(d->pPreviewsGenerator, SIGNAL(started()), u->frmMain, SLOT(hide()));
connect(d->pPreviewsGenerator, SIGNAL(progress(int)), u->prgGenPreviews, SLOT(setValue(int)));
connect(d->pPreviewsGenerator, SIGNAL(previewGenerated(const KLFLatexSymbol&, const QPixmap &)),
d, SLOT(previewGenerated(const KLFLatexSymbol & , const QPixmap & )));
connect(u->btnGenPreviewsSkip, SIGNAL(clicked()), d, SLOT(skipGenPreviews()));
connect(d->pPreviewsGenerator, SIGNAL(finished()), d, SLOT(generatePreviewsFinished()));
// add our search bar
KLF_DEBUG_ASSIGN_REF_INSTANCE(u->searchBar, "latexsymbols-searchbar") ;
// u->searchBar->setShowOverlayMode(true);
u->searchBar->registerShortcuts(this);
u->searchBar->setShowHideButton(true);
connect(u->searchBar, SIGNAL(escapePressed()), u->searchBar, SLOT(hide()));
d->pSearchable = new KLFLatexSymbolsSearchable(this);
u->searchBar->setSearchTarget(d->pSearchable);
u->btnInsertSearchCurrent->hide();
klfDbg("prepared search bar.") ;
KLFRelativeFont *relfont = new KLFRelativeFont(u->cbxCategory);
relfont->setRelPointSize(+2);
connect(u->cbxCategory, SIGNAL(highlighted(int)), this, SLOT(slotShowCategory(int)));
connect(u->cbxCategory, SIGNAL(activated(int)), this, SLOT(slotShowCategory(int)));
connect(u->btnClose, SIGNAL(clicked()), this, SLOT(close()));
connect(u->btnInsertSearchCurrent, SIGNAL(clicked()),
d->pSearchable, SLOT(slotInsertCurrentMatch()));
#ifdef KLF_WS_MAC
// Cmd+Space is used by spotlight, so use Ctrl+Space (on Qt/Mac, Ctrl key is mapped to Qt::META)
u->btnInsertSearchCurrent->setShortcut(QKeySequence(Qt::META + Qt::Key_Space));
#endif
}
void KLFLatexSymbols::retranslateUi(bool alsoBaseUi)
{
if (alsoBaseUi) {
u->retranslateUi(this);
}
}
KLFLatexSymbols::~KLFLatexSymbols()
{
KLF_DEBUG_BLOCK(KLF_FUNC_NAME) ;
d->pCache->saveToKlfCache();
klfDbg("thread interrupt ...") ;
d->workerThread.requestInterruption();
klfDbg("thread quit ...") ;
d->workerThread.quit();// although it seems quit() needs to be called from within the thread... ??
klfDbg("thread wait ...") ;
d->workerThread.wait();
klfDbg("thread done.") ;
KLF_DELETE_PRIVATE ;
}
void KLFLatexSymbols::load()
{
KLF_DEBUG_BLOCK(KLF_FUNC_NAME) ;
// read our config and create the UI
d->read_symbols_create_ui();
slotShowCategory(0);
slotKlfConfigChanged();
}
void KLFLatexSymbols::emitInsertSymbol(const KLFLatexSymbol& symbol)
{
emit insertSymbol(symbol);
}
void KLFLatexSymbols::slotKlfConfigChanged()
{
u->searchBar->setEmacsStyleBackspace(klfconfig.UI.emacsStyleBackspaceSearch);
}
KLFLatexSymbolsCache * KLFLatexSymbols::cache()
{
return d->pCache;
}
QList<KLFLatexSymbolsView *> KLFLatexSymbols::categoryViews()
{
return d->mViews;
}
int KLFLatexSymbols::currentCategoryIndex() const
{
return u->cbxCategory->currentIndex();
}
void KLFLatexSymbolsPrivate::read_symbols_create_ui()
{
KLF_DEBUG_TIME_BLOCK(KLF_FUNC_NAME) ;
// create our UI
K->u->cbxCategory->clear();
// clear any existing views
foreach (KLFLatexSymbolsView * view, mViews) {
K->u->stkViews->removeWidget(view);
}
KLF_ASSERT_CONDITION(K->u->stkViews->count() == 0,
"Widgets still left in view stack !! symbol categories will be wrong", ) ;
mViews.clear();
// read symbols config
// find collection of XML files
QStringList fxmllist;
// in the following directories
QStringList fxmldirs;
fxmldirs << klfconfig.homeConfigDir + "/conf/latexsymbols.d/"
<< klfconfig.globalShareDir + "/conf/latexsymbols.d/"
<< ":/conf/latexsymbols.d/";
klfDbgT("starting to collect XML files from dirs "<<fxmldirs) ;
// collect all XML files
int k, j;
for (k = 0; k < fxmldirs.size(); ++k) {
QDir fxmldir(fxmldirs[k]);
QStringList xmllist = fxmldir.entryList(QStringList()<<"*.xml", QDir::Files);
for (j = 0; j < xmllist.size(); ++j)
fxmllist << fxmldir.absoluteFilePath(xmllist[j]);
}
klfDbgT("files collected: "<<fxmllist) ;
klfDbgT("got xml files, ensured not empty; fxmllist="<<fxmllist) ;
// this will be a full list of symbols to feed to the cache
QList<KLFLatexSymbol> allsymbols;
// same indexes as in mViews[]
QStringList categoryTitleLangs;
// now read the file list
for (k = 0; k < fxmllist.size(); ++k) {
KLF_DEBUG_TIME_BLOCK(KLF_FUNC_NAME+"/fxmllist["+('0'+k)+"]");
klfDbg("reading XML file="<<fxmllist[k]);
QString fn = fxmllist[k];
QFile file(fn);
if ( ! file.open(QIODevice::ReadOnly) ) {
qWarning()<<KLF_FUNC_NAME<<": Error: Can't open latex symbols XML file "<<fn<<": "<<file.errorString()<<"!";
continue;
}
QDomDocument doc("latexsymbols");
QString errMsg; int errLine, errCol;
bool r = doc.setContent(&file, false, &errMsg, &errLine, &errCol);
if (!r) {
qWarning()<<KLF_FUNC_NAME<<": Error parsing file "<<fn<<": "<<errMsg<<" at line "<<errLine<<", col "<<errCol;
continue;
}
file.close();
QDomElement root = doc.documentElement();
if (root.nodeName() == "latexsymbollist") {
if (!fn.endsWith("conf/latexsymbols.d/defaultlatexsymbols.xml"))
klfWarning("Ignoring old symbols definition file "<<fn<<".");
continue;
}
if (root.nodeName() != "klf-symbol-list") {
qWarning("%s: Error parsing XML for latex symbols from file `%s': unexpected root tag `%s'.", KLF_FUNC_NAME,
qPrintable(fn), qPrintable(root.nodeName()));
continue;
}
QString minklfversion = root.attribute("minklfversion", "3.3.0a");
if (klfVersionCompare(minklfversion, KLF_VERSION_STRING) > 0) {
qWarning("%s: ignoring XML latex symbols definition file `%s' which requires KLF version %s.",
KLF_FUNC_NAME, qPrintable(fn), qPrintable(minklfversion));
continue;
}
QDomNode n;
for (n = root.firstChild(); ! n.isNull(); n = n.nextSibling()) {
QDomElement e = n.toElement(); // try to convert the node to an element.
if ( e.isNull() || n.nodeType() != QDomNode::ElementNode )
continue;
if ( e.nodeName() != "category" ) {
qWarning("WARNING in parsing XML : ignoring unexpected tag `%s'!\n",
qPrintable(e.nodeName()));
continue;
}
// read category
QString heading = e.attribute("name");
QString categoryTitle;
// xml:lang attribute for category title is obsolete, we use now Qt-linguist translated value...
QString curCategoryTitleLang;
QList<KLFLatexSymbol> l;
QDomNode esym;
for (esym = e.firstChild(); ! esym.isNull(); esym = esym.nextSibling() ) {
if ( esym.isNull() || esym.nodeType() != QDomNode::ElementNode )
continue;
QDomElement eesym = esym.toElement();
klfDbg("read element "<<esym.nodeName());
if ( eesym.nodeName() == "category-title" ) {
// xml:lang attribute for category title is obsolete, we use now Qt-linguist translated value...
QString lang = eesym.hasAttribute("xml:lang") ? eesym.attribute("xml:lang") : QString() ;
klfDbg("<category-title>: lang="<<lang<<"; hasAttribute(xml:lang)="<<eesym.hasAttribute("xml:lang")
<<"; current category-title="<<categoryTitle<<",lang="<<curCategoryTitleLang) ;
if (categoryTitle.isEmpty()) {
// no category title yet
if (lang.isEmpty() || lang.startsWith(klfconfig.UI.locale) || klfconfig.UI.locale().startsWith(lang)) {
// correct locale
categoryTitle = qApp->translate("xmltr_latexsymbols", eesym.text().toUtf8().constData(),
"[[tag: <category-title>]]");
curCategoryTitleLang = lang;
}
// otherwise skip this tag
} else {
// see if this locale is correct and more specific
if ( (lang.startsWith(klfconfig.UI.locale) || klfconfig.UI.locale().startsWith(lang)) &&
(curCategoryTitleLang.isEmpty() || lang.startsWith(curCategoryTitleLang) ) ) {
// then keep it and replace the other
categoryTitle = eesym.text();
curCategoryTitleLang = lang;
}
// otherwise skip this tag
}
continue;
}
if ( esym.nodeName() != "sym" ) {
qWarning("%s: WARNING in parsing XML : ignoring unexpected tag `%s' in category `%s'!\n",
KLF_FUNC_NAME, qPrintable(esym.nodeName()), qPrintable(heading));
continue;
}
// read symbol
KLFLatexSymbol sym(eesym);
l.append(sym);
if (!pCache->contains(sym)) {
klfDbg("Adding symbol " << sym << " to generate-pixmap-list") ;
allsymbols.append(sym);
}
}
// and add this category, or append to existing category
KLFLatexSymbolsView * view = NULL;
for (j = 0; j < mViews.size(); ++j) {
if (mViews[j]->category() == heading) {
view = mViews[j];
break;
}
}
if (view == NULL) {
// category does not yet exist
klfDbg("Adding view for category " << heading) ;
view = new KLFLatexSymbolsView(heading, NULL);
connect(view, SIGNAL(symbolActivated(const KLFLatexSymbol&)),
K, SIGNAL(insertSymbol(const KLFLatexSymbol&))) ;
mViews.append(view);
K->u->stkViews->addWidget(view);
if (categoryTitle.isEmpty()) {
categoryTitle = heading;
}
K->u->cbxCategory->addItem(categoryTitle, heading);
categoryTitleLangs << curCategoryTitleLang;
} else {
klfDbg("Updating view for category " << heading) ;
// possibly update the title if a better translation is available
if (!categoryTitle.isEmpty() &&
(categoryTitleLangs[j].isEmpty() || curCategoryTitleLang.startsWith(categoryTitleLangs[j]))) {
// update the title
K->u->cbxCategory->setItemText(j, categoryTitle);
} else {
// keep old title
}
}
view->appendSymbolList(l);
} // iterate over categories in XML file
} // iterate over XML files
klfDbg("prepared symbols list, about to start worker thread") ;
workerThread.start();
klfDbg("worker thread started, about to call generatePreviewList (queued connection)") ;
QMetaObject::invokeMethod(pPreviewsGenerator, "generatePreviewList", Qt::QueuedConnection,
Q_ARG(QList<KLFLatexSymbol>, allsymbols)) ;
}
void KLFLatexSymbolsPrivate::previewGenerated(const KLFLatexSymbol & symbol, const QPixmap & pixmap)
{
KLF_DEBUG_BLOCK(KLF_FUNC_NAME) ;
klfDbg("setting pixmap for symbol " << symbol.symbol) ;
pCache->setSymbolPixmap(symbol, pixmap);
}
void KLFLatexSymbolsPrivate::skipGenPreviews()
{
KLF_DEBUG_BLOCK(KLF_FUNC_NAME) ;
workerThread.requestInterruption();
// build whatever UI we can
foreach (KLFLatexSymbolsView * view, mViews) {
view->buildDisplay(pCache);
}
K->u->wGenPreviews->hide();
K->u->frmMain->show();
}
void KLFLatexSymbolsPrivate::generatePreviewsFinished()
{
KLF_DEBUG_BLOCK(KLF_FUNC_NAME) ;
klfDbg("stopping thread.") ;
workerThread.quit();
#ifdef KLF_DEBUG
// and dump symbol cache
pCache->debugDump();
#endif
foreach (KLFLatexSymbolsView * view, mViews) {
klfDbg("setting up display for category " << view->category()) ;
view->buildDisplay(pCache);
}
klfDbg("hiding wGenPreviews widget") ;
K->u->wGenPreviews->hide();
K->u->frmMain->show();
}
void KLFLatexSymbols::slotShowCategory(int c)
{
KLF_DEBUG_BLOCK(KLF_FUNC_NAME) ;
// called by combobox
u->stkViews->setCurrentIndex(c);
klfDbg("current index="<<c) ;
}
void KLFLatexSymbols::closeEvent(QCloseEvent *e)
{
e->accept();
}
void KLFLatexSymbols::showEvent(QShowEvent *e)
{
QWidget::showEvent(e);
}
bool KLFLatexSymbols::event(QEvent *e)
{
if (e->type() == QEvent::Polish) {
u->cbxCategory->setMinimumHeight(u->cbxCategory->sizeHint().height()+5);
}
if (e->type() == QEvent::KeyPress) {
QKeyEvent *ke = (QKeyEvent*)e;
if (ke->key() == Qt::Key_F7 && ke->modifiers() == 0) {
hide();
e->accept();
return true;
}
}
return QWidget::event(e);
}