/* This is part of TeXworks, an environment for working with TeX documents Copyright (C) 2007-08 Jonathan Kew 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, see . For links to further information, or to contact the author, see . */ #include "TWUtils.h" #include "TWApp.h" #include "TeXDocument.h" #include "PDFDocument.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #pragma mark === TWUtils === bool TWUtils::isPDFfile(const QString& fileName) { QFile theFile(fileName); if (theFile.open(QIODevice::ReadOnly)) { QByteArray ba = theFile.peek(8); if (ba.startsWith("%PDF-1.")) return true; } return false; } bool TWUtils::isImageFile(const QString& fileName) { QImage image(fileName); return !image.isNull(); } bool TWUtils::isPostscriptFile(const QString& fileName) { QFile theFile(fileName); if (theFile.open(QIODevice::ReadOnly)) { QByteArray ba = theFile.peek(4); if (ba.startsWith("%!PS")) return true; } return false; } const QString TWUtils::getLibraryPath(const QString& subdir) { QString libPath; libPath = TWApp::instance()->getPortableLibPath(); if (!libPath.isEmpty()) { libPath = QDir(libPath).absolutePath() + QDir::separator() + subdir; } else { #ifdef Q_WS_MAC libPath = QDir::homePath() + "/Library/" + TEXWORKS_NAME + "/" + subdir; #endif #ifdef Q_WS_X11 if (subdir == "dictionaries") libPath = "/usr/share/myspell/dicts"; else libPath = QDir::homePath() + "/." + TEXWORKS_NAME + "/" + subdir; #endif #ifdef Q_WS_WIN libPath = QDir::homePath() + "/" + TEXWORKS_NAME + "/" + subdir; #endif } // check if libPath exists QFileInfo info(libPath); if (!info.exists()) { // create libPath if (QDir::root().mkpath(libPath)) { if (subdir != "translations") { // don't copy the built-in translations QString cwd = QDir::currentPath(); if (QDir::setCurrent(libPath)) { // copy default contents from app resources into the library dir QDir resDir(":/resfiles/" + subdir); copyResources(resDir, libPath); } QDir::setCurrent(cwd); } } } return libPath; } void TWUtils::copyResources(const QDir& resDir, const QString& libPath) { QDirIterator iter(resDir, QDirIterator::Subdirectories); while (iter.hasNext()) { (void)iter.next(); if (iter.fileInfo().isDir()) continue; QString destPath = iter.fileInfo().canonicalPath(); destPath.replace(resDir.path(), libPath); QFileInfo dest(destPath); if (!dest.exists()) if (!QDir::root().mkpath(destPath)) continue; if (QDir::setCurrent(destPath)) { QFile srcFile(iter.fileInfo().canonicalFilePath()); srcFile.copy(iter.fileName()); QDir::setCurrent(libPath); } } } static int insertItemIfPresent(QFileInfo& fi, QMenu* helpMenu, QAction* before, QSignalMapper* mapper, QString title) { QFileInfo indexFile(fi.absoluteFilePath(), "index.html"); if (indexFile.exists()) { QFileInfo titlefileInfo(fi.absoluteFilePath(), "tw-help-title.txt"); if (titlefileInfo.exists() && titlefileInfo.isReadable()) { QFile titleFile(titlefileInfo.absoluteFilePath()); titleFile.open(QIODevice::ReadOnly | QIODevice::Text); QTextStream titleStream(&titleFile); titleStream.setCodec("UTF-8"); title = titleStream.readLine(); } QAction* action = new QAction(title, helpMenu); mapper->setMapping(action, fi.canonicalFilePath()); action->connect(action, SIGNAL(triggered()), mapper, SLOT(map())); helpMenu->insertAction(before, action); return 1; } return 0; } void TWUtils::insertHelpMenuItems(QMenu* helpMenu) { QSignalMapper* mapper = new QSignalMapper(helpMenu); mapper->connect(mapper, SIGNAL(mapped(const QString&)), TWApp::instance(), SLOT(openHelpFile(const QString&))); QAction* before = NULL; int i, firstSeparator = 0; QList actions = helpMenu->actions(); for (i = 0; i < actions.count(); ++i) { if (actions[i]->isSeparator() && !firstSeparator) firstSeparator = i; if (actions[i]->menuRole() == QAction::AboutRole) { before = actions[i]; break; } } while (--i > firstSeparator) { helpMenu->removeAction(actions[i]); delete actions[i]; } #ifdef Q_WS_MAC QDir helpDir(QCoreApplication::applicationDirPath() + "/../texworks-help"); #else QDir helpDir(QCoreApplication::applicationDirPath() + "/texworks-help"); #ifdef Q_WS_X11 if (!helpDir.exists()) #ifdef TW_HELPPATH helpDir.cd(TW_HELPPATH); #else helpDir.cd("/usr/local/share/texworks-help"); #endif // defined(TW_HELPPATH) #endif #endif const char* helpPath = getenv("TW_HELPPATH"); if (helpPath != NULL) helpDir.cd(QString(helpPath)); QSETTINGS_OBJECT(settings); QString loc = settings.value("locale").toString(); if (loc.isEmpty()) loc = QLocale::system().name(); QDirIterator iter(helpDir); int inserted = 0; while (iter.hasNext()) { (void)iter.next(); if (!iter.fileInfo().isDir()) continue; QString name(iter.fileInfo().fileName()); if (name == "." || name == "..") continue; QDir subDir(iter.filePath()); // try for localized content first QFileInfo fi(subDir, loc); if (fi.exists() && fi.isDir() && fi.isReadable()) { inserted += insertItemIfPresent(fi, helpMenu, before, mapper, name); continue; } fi.setFile(subDir.absolutePath() + "/" + loc.left(2)); if (fi.exists() && fi.isDir() && fi.isReadable()) { inserted += insertItemIfPresent(fi, helpMenu, before, mapper, name); continue; } fi.setFile(subDir.absolutePath() + "/en"); if (fi.exists() && fi.isDir() && fi.isReadable()) { inserted += insertItemIfPresent(fi, helpMenu, before, mapper, name); continue; } fi.setFile(subDir.absolutePath()); inserted += insertItemIfPresent(fi, helpMenu, before, mapper, name); } if (inserted > 0) { QAction* sep = new QAction(helpMenu); sep->setSeparator(true); helpMenu->insertAction(before, sep); } } QList *TWUtils::codecList = NULL; QList *TWUtils::findCodecs() { if (codecList != NULL) return codecList; codecList = new QList; QMap codecMap; QRegExp iso8859RegExp("ISO[- ]8859-([0-9]+).*"); foreach (int mib, QTextCodec::availableMibs()) { QTextCodec *codec = QTextCodec::codecForMib(mib); QString sortKey = codec->name().toUpper(); int rank; if (sortKey.startsWith("UTF-8")) rank = 1; else if (sortKey.startsWith("UTF-16")) rank = 2; else if (iso8859RegExp.exactMatch(sortKey)) { if (iso8859RegExp.cap(1).size() == 1) rank = 3; else rank = 4; } else rank = 5; sortKey.prepend(QChar('0' + rank)); codecMap.insert(sortKey, codec); } *codecList = codecMap.values(); return codecList; } QStringList* TWUtils::translationList = NULL; QStringList* TWUtils::getTranslationList() { if (translationList != NULL) return translationList; translationList = new QStringList; QDir transDir(":/resfiles/translations"); foreach (QFileInfo qmFileInfo, transDir.entryInfoList(QStringList(TEXWORKS_NAME "_*.qm"), QDir::Files | QDir::Readable, QDir::Name | QDir::IgnoreCase)) { QString locName = qmFileInfo.completeBaseName(); locName.remove(TEXWORKS_NAME "_"); *translationList << locName; } transDir = QDir(TWUtils::getLibraryPath("translations")); foreach (QFileInfo qmFileInfo, transDir.entryInfoList(QStringList(TEXWORKS_NAME "_*.qm"), QDir::Files | QDir::Readable, QDir::Name | QDir::IgnoreCase)) { QString locName = qmFileInfo.completeBaseName(); locName.remove(TEXWORKS_NAME "_"); if (!translationList->contains(locName, Qt::CaseInsensitive)) *translationList << locName; } return translationList; } QStringList* TWUtils::dictionaryList = NULL; QStringList* TWUtils::getDictionaryList() { if (dictionaryList != NULL) return dictionaryList; dictionaryList = new QStringList; QDir dicDir(TWUtils::getLibraryPath("dictionaries")); foreach (QFileInfo affFileInfo, dicDir.entryInfoList(QStringList("*.aff"), QDir::Files | QDir::Readable, QDir::Name | QDir::IgnoreCase)) { QFileInfo dicFileInfo(affFileInfo.dir(), affFileInfo.completeBaseName() + ".dic"); if (dicFileInfo.isReadable()) *dictionaryList << dicFileInfo.completeBaseName(); } return dictionaryList; } QHash *TWUtils::dictionaries = NULL; Hunhandle* TWUtils::getDictionary(const QString& language) { if (language.isEmpty()) return NULL; if (dictionaries == NULL) dictionaries = new QHash; if (dictionaries->contains(language)) return dictionaries->value(language); Hunhandle *h = NULL; const QString dictPath = getLibraryPath("dictionaries"); QFileInfo affFile(dictPath + "/" + language + ".aff"); QFileInfo dicFile(dictPath + "/" + language + ".dic"); if (affFile.isReadable() && dicFile.isReadable()) { h = Hunspell_create(affFile.canonicalFilePath().toLocal8Bit().data(), dicFile.canonicalFilePath().toLocal8Bit().data()); (*dictionaries)[language] = h; } return h; } QStringList* TWUtils::filters; QStringList* TWUtils::filterList() { return filters; } void TWUtils::setDefaultFilters() { *filters << QObject::tr("TeX documents (*.tex)"); *filters << QObject::tr("LaTeX documents (*.ltx)"); *filters << QObject::tr("BibTeX databases (*.bib)"); *filters << QObject::tr("Style files (*.sty)"); *filters << QObject::tr("Class files (*.cls)"); *filters << QObject::tr("Documented macros (*.dtx)"); *filters << QObject::tr("Auxiliary files (*.aux *.toc *.lot *.lof *.nav *.out *.snm *.ind *.idx *.bbl *.log)"); *filters << QObject::tr("Text files (*.txt)"); *filters << QObject::tr("PDF documents (*.pdf)"); #ifdef Q_WS_WIN *filters << QObject::tr("All files") + " (*.*)"; // unfortunately this doesn't work nicely on OS X or X11 #else *filters << QObject::tr("All files") + " (*)"; #endif } QString TWUtils::strippedName(const QString &fullFileName) { return QFileInfo(fullFileName).fileName(); } void TWUtils::updateRecentFileActions(QObject *parent, QList &actions, QMenu *menu) /* static */ { QSETTINGS_OBJECT(settings); QStringList files = settings.value("recentFileList").toStringList(); int numRecentFiles = files.size(); while (actions.size() < numRecentFiles) { QAction *act = new QAction(parent); act->setVisible(false); QObject::connect(act, SIGNAL(triggered()), qApp, SLOT(openRecentFile())); actions.append(act); menu->addAction(act); } while (actions.size() > numRecentFiles) { QAction *act = actions.takeLast(); delete act; } for (int i = 0; i < numRecentFiles; ++i) { QString text = TWUtils::strippedName(files[i]); actions[i]->setText(text); actions[i]->setData(files[i]); actions[i]->setVisible(true); } } void TWUtils::updateWindowMenu(QWidget *window, QMenu *menu) /* static */ { // shorten the menu by removing everything from the first "selectWindow" action onwards QList actions = menu->actions(); for (QList::iterator i = actions.begin(); i != actions.end(); ++i) { SelWinAction *selWin = qobject_cast(*i); if (selWin) menu->removeAction(*i); } while (!menu->actions().isEmpty() && menu->actions().last()->isSeparator()) menu->removeAction(menu->actions().last()); // append an item for each TeXDocument bool first = true; foreach (TeXDocument *texDoc, TeXDocument::documentList()) { if (first && !menu->actions().isEmpty()) menu->addSeparator(); first = false; SelWinAction *selWin = new SelWinAction(menu, texDoc->fileName()); if (texDoc == qobject_cast(window)) { selWin->setCheckable(true); selWin->setChecked(true); } QObject::connect(selWin, SIGNAL(triggered()), texDoc, SLOT(selectWindow())); menu->addAction(selWin); } // append an item for each PDFDocument first = true; foreach (PDFDocument *pdfDoc, PDFDocument::documentList()) { if (first && !menu->actions().isEmpty()) menu->addSeparator(); first = false; SelWinAction *selWin = new SelWinAction(menu, pdfDoc->fileName()); if (pdfDoc == qobject_cast(window)) { selWin->setCheckable(true); selWin->setChecked(true); } QObject::connect(selWin, SIGNAL(triggered()), pdfDoc, SLOT(selectWindow())); menu->addAction(selWin); } } void TWUtils::ensureOnScreen(QWidget *window) { QDesktopWidget *desktop = QApplication::desktop(); QRect screenRect = desktop->availableGeometry(window); QRect adjustedFrame = window->frameGeometry(); if (adjustedFrame.width() > screenRect.width()) adjustedFrame.setWidth(screenRect.width()); if (adjustedFrame.height() > screenRect.height()) adjustedFrame.setHeight(screenRect.height()); if (adjustedFrame.left() < screenRect.left()) adjustedFrame.moveLeft(screenRect.left()); else if (adjustedFrame.right() > screenRect.right()) adjustedFrame.moveRight(screenRect.right()); if (adjustedFrame.top() < screenRect.top()) adjustedFrame.moveTop(screenRect.top()); else if (adjustedFrame.bottom() > screenRect.bottom()) adjustedFrame.moveBottom(screenRect.bottom()); if (adjustedFrame != window->frameGeometry()) window->setGeometry(adjustedFrame.adjusted(window->geometry().left() - window->frameGeometry().left(), window->geometry().top() - window->frameGeometry().top(), window->frameGeometry().right() - window->geometry().right(), window->frameGeometry().bottom() - window->geometry().bottom() )); } void TWUtils::zoomToScreen(QWidget *window) { QDesktopWidget *desktop = QApplication::desktop(); QRect screenRect = desktop->availableGeometry(window); screenRect.setTop(screenRect.top() + window->geometry().y() - window->y()); window->setGeometry(screenRect); } void TWUtils::zoomToHalfScreen(QWidget *window, bool rhs) { QDesktopWidget *desktop = QApplication::desktop(); QRect r = desktop->availableGeometry(window); int wDiff = window->frameGeometry().width() - window->width(); int hDiff = window->frameGeometry().height() - window->height(); if (rhs) { r.setLeft(r.left() + r.right() / 2); window->move(r.left(), r.top()); window->resize(r.width() - wDiff, r.height() - hDiff); } else { r.setRight(r.left() + r.right() / 2 - 1); window->move(r.left(), r.top()); window->resize(r.width() - wDiff, r.height() - hDiff); } } void TWUtils::sideBySide(QWidget *window1, QWidget *window2) { zoomToHalfScreen(window1, false); zoomToHalfScreen(window2, true); } void TWUtils::tileWindowsInRect(const QWidgetList& windows, const QRect& bounds) { int numWindows = windows.count(); int rows = 1, cols = 1; while (rows * cols < numWindows) if (rows == cols) ++cols; else ++rows; QRect r; r.setWidth(bounds.width() / cols); r.setHeight(bounds.height() / rows); r.moveLeft(bounds.left()); r.moveTop(bounds.top()); int x = 0, y = 0; foreach (QWidget* window, windows) { int wDiff = window->frameGeometry().width() - window->width(); int hDiff = window->frameGeometry().height() - window->height(); window->move(r.left(), r.top()); window->resize(r.width() - wDiff, r.height() - hDiff); if (window->isMinimized()) window->showNormal(); if (++x == cols) { x = 0; ++y; r.moveLeft(bounds.left()); r.moveTop(bounds.top() + (bounds.height() * y) / rows); } else r.moveLeft(bounds.left() + (bounds.width() * x) / cols); } } void TWUtils::stackWindowsInRect(const QWidgetList& windows, const QRect& bounds) { const int kStackingOffset = 20; QRect r(bounds); r.setWidth(r.width() / 2); int index = 0; foreach (QWidget* window, windows) { int wDiff = window->frameGeometry().width() - window->width(); int hDiff = window->frameGeometry().height() - window->height(); window->move(r.left(), r.top()); window->resize(r.width() - wDiff, r.height() - hDiff); if (window->isMinimized()) window->showNormal(); r.moveLeft(r.left() + kStackingOffset); if (r.right() > bounds.right()) { r = bounds; r.setWidth(r.width() / 2); index = 0; } else if (++index == 10) { r.setTop(bounds.top()); index = 0; } else { r.setTop(r.top() + kStackingOffset); if (r.height() < bounds.height() / 2) { r.setTop(bounds.top()); index = 0; } } } } void TWUtils::applyToolbarOptions(QMainWindow *theWindow, int iconSize, bool showText) { iconSize = iconSize * 8 + 8; // convert 1,2,3 to 16,24,32 foreach (QObject *object, theWindow->children()) { QToolBar *theToolBar = qobject_cast(object); if (theToolBar != NULL) { theToolBar->setToolButtonStyle(showText ? Qt::ToolButtonTextUnderIcon : Qt::ToolButtonIconOnly); theToolBar->setIconSize(QSize(iconSize, iconSize)); } } } bool TWUtils::findNextWord(const QString& text, int index, int& start, int& end) { // try to do a sensible "word" selection for TeX documents, taking account of the form of control sequences: // given an index representing a caret, // if current char (following caret) is letter, apostrophe, or '@', extend in both directions // include apostrophe if surrounded by letters // include preceding backslash if any, unless word contains apostrophe // if preceding char is backslash, extend to include backslash only // if current char is number, extend in both directions // if current char is space or tab, extend in both directions to include all spaces or tabs // if current char is backslash, include next char; if letter or '@', extend to include all following letters or '@' // else select single char following index // returns TRUE if the resulting selection consists of word-forming chars start = end = index; if (text.length() < 1) // empty return false; if (index >= text.length()) // end of line return false; QChar ch = text.at(index); #define IS_WORD_FORMING(ch) (ch.isLetter() || ch.isMark()) bool isControlSeq = false; // becomes true if we include an @ sign or a leading backslash bool includesApos = false; // becomes true if we include an apostrophe if (IS_WORD_FORMING(ch) || ch == '@' /* || ch == '\'' || ch == 0x2019 */) { if (ch == '@') isControlSeq = true; //else if (ch == '\'' || ch == 0x2019) // includesApos = true; while (start > 0) { --start; ch = text.at(start); if (IS_WORD_FORMING(ch)) continue; if (!includesApos && ch == '@') { isControlSeq = true; continue; } if (!isControlSeq && (ch == '\'' || ch == 0x2019) && start > 0 && IS_WORD_FORMING(text.at(start - 1))) { includesApos = true; continue; } ++start; break; } if (start > 0 && text.at(start - 1) == '\\') { isControlSeq = true; --start; } while (++end < text.length()) { ch = text.at(end); if (IS_WORD_FORMING(ch)) continue; if (!includesApos && ch == '@') { isControlSeq = true; continue; } if (!isControlSeq && (ch == '\'' || ch == 0x2019) && end < text.length() - 1 && IS_WORD_FORMING(text.at(end + 1))) { includesApos = true; continue; } break; } return !isControlSeq; } if (index > 0 && text.at(index - 1) == '\\') { start = index - 1; end = index + 1; return false; } if (ch.isNumber()) { // TODO: handle decimals, leading signs while (start > 0) { --start; ch = text.at(start); if (ch.isNumber()) continue; ++start; break; } while (++end < text.length()) { ch = text.at(end); if (ch.isNumber()) continue; break; } return false; } if (ch == ' ' || ch == '\t') { while (start > 0) { --start; ch = text.at(start); if (!(ch == ' ' || ch == '\t')) { ++start; break; } } while (++end < text.length()) { ch = text.at(end); if (!(ch == ' ' || ch == '\t')) break; } return false; } if (ch == '\\') { if (++end < text.length()) { ch = text.at(end); if (IS_WORD_FORMING(ch) || ch == '@') while (++end < text.length()) { ch = text.at(end); if (IS_WORD_FORMING(ch) || ch == '@') continue; break; } else ++end; } return false; } // else the character is selected in isolation end = index + 1; return false; } QMap TWUtils::pairOpeners; QMap TWUtils::pairClosers; QChar TWUtils::closerMatching(QChar c) { return pairClosers.value(c); } QChar TWUtils::openerMatching(QChar c) { return pairOpeners.value(c); } QString TWUtils::sIncludePdfCommand; QString TWUtils::sIncludeImageCommand; QString TWUtils::sIncludePostscriptCommand; QString TWUtils::sIncludeTextCommand; QString TWUtils::sCleanupPatterns; const QString& TWUtils::includePdfCommand() { return sIncludePdfCommand; } const QString& TWUtils::includeImageCommand() { return sIncludeImageCommand; } const QString& TWUtils::includePostscriptCommand() { return sIncludePostscriptCommand; } const QString& TWUtils::includeTextCommand() { return sIncludeTextCommand; } const QString& TWUtils::cleanupPatterns() { return sCleanupPatterns; } void TWUtils::readConfig() { pairOpeners.clear(); pairClosers.clear(); QDir configDir(TWUtils::getLibraryPath("configuration")); QRegExp pair("([^\\s])\\s+([^\\s])\\s*(?:#.*)?"); QFile pairsFile(configDir.filePath("delimiter-pairs.txt")); if (pairsFile.open(QIODevice::ReadOnly)) { while (1) { QByteArray ba = pairsFile.readLine(); if (ba.size() == 0) break; if (ba[0] == '#' || ba[0] == '\n') continue; ba.chop(1); QString line = QString::fromUtf8(ba.data(), ba.size()); if (pair.exactMatch(line)) { pairClosers[pair.cap(1).at(0)] = pair.cap(2).at(0); pairOpeners[pair.cap(2).at(0)] = pair.cap(1).at(0); } } } // defaults in case config file not found sIncludeTextCommand = "\\include{%1}\n"; sIncludePdfCommand = "\\includegraphics[]{%1}\n"; sIncludeImageCommand = "\\includegraphics[]{%1}\n"; sIncludePostscriptCommand = "\\includegraphics[]{%1}\n"; sCleanupPatterns = "*.aux $jobname.log $jobname.lof $jobname.lot $jobname.toc"; filters = new QStringList; QFile configFile(configDir.filePath("texworks-config.txt")); if (configFile.open(QIODevice::ReadOnly)) { QRegExp keyVal("([-a-z]+):\\s*([^ \\t].+)"); // looking for keyword, colon, optional whitespace, value while (1) { QByteArray ba = configFile.readLine(); if (ba.size() == 0) break; if (ba[0] == '#' || ba[0] == '\n') continue; QString line = QString::fromUtf8(ba.data(), ba.size()); if (keyVal.exactMatch(line)) { // if that matched, keyVal.cap(1) is the keyword, cap(2) is the value const QString& keyword = keyVal.cap(1); QString value = keyVal.cap(2).trimmed(); if (keyword == "include-text") { sIncludeTextCommand = value.replace("#RET#", "\n"); continue; } if (keyword == "include-pdf") { sIncludePdfCommand = value.replace("#RET#", "\n"); continue; } if (keyword == "include-image") { sIncludeImageCommand = value.replace("#RET#", "\n"); continue; } if (keyword == "include-postscript") { sIncludePostscriptCommand = value.replace("#RET#", "\n"); continue; } if (keyword == "cleanup-patterns") { static bool first = true; if (first) { sCleanupPatterns = value; first = false; } else { sCleanupPatterns += " "; sCleanupPatterns += value; } continue; } if (keyword == "file-open-filter") { *filters << value; } } } } if (filters->count() == 0) setDefaultFilters(); } int TWUtils::balanceDelim(const QString& text, int pos, QChar delim, int direction) { int len = text.length(); QChar c; while ((c = text[pos]) != delim) { if (openerMatching(c) != 0) pos = (direction < 0) ? balanceDelim(text, pos - 1, openerMatching(c), -1) : -1; else if (closerMatching(c) != 0) pos = (direction > 0) ? balanceDelim(text, pos + 1, closerMatching(c), 1) : -1; if (pos < 0) return -1; pos += direction; if (pos < 0 || pos >= len) return -1; } return pos; } int TWUtils::findOpeningDelim(const QString& text, int pos) // find the first opening delimiter before offset /pos/ { while (--pos >= 0) { QChar c = text[pos]; if (closerMatching(c) != 0) return pos; } return -1; } void TWUtils::installCustomShortcuts(QWidget * widget, bool recursive) { if (widget == NULL) return; QString filename = QDir(TWUtils::getLibraryPath("configuration")).absoluteFilePath("shortcuts.ini"); if (filename.isEmpty()) return; QSettings map(filename, QSettings::IniFormat); if (map.status() != QSettings::NoError) return; QAction * act; foreach (act, widget->actions()) { if (act->objectName().isEmpty()) continue; if (map.contains(act->objectName())) act->setShortcut(QKeySequence(map.value(act->objectName()).toString())); } if (recursive) { QObject * obj; foreach (obj, widget->children()) { QWidget * child = qobject_cast(obj); if (child) installCustomShortcuts(child); } } } #pragma mark === SelWinAction === // action subclass used for dynamic window-selection items in the Window menu SelWinAction::SelWinAction(QObject *parent, const QString &fileName) : QAction(parent) { setText(TWUtils::strippedName(fileName)); setData(fileName); } #pragma mark === CmdKeyFilter === // on OS X only, the singleton CmdKeyFilter object is attached to all TeXDocument editor widgets // to stop Command-keys getting inserted into edit text items CmdKeyFilter *CmdKeyFilter::filterObj = NULL; CmdKeyFilter *CmdKeyFilter::filter() { if (filterObj == NULL) filterObj = new CmdKeyFilter; return filterObj; } bool CmdKeyFilter::eventFilter(QObject *obj, QEvent *event) { #ifdef Q_WS_MAC if (event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast(event); if ((keyEvent->modifiers() & Qt::ControlModifier) != 0) { if (keyEvent->key() <= 0x0ff && keyEvent->key() != Qt::Key_Z && keyEvent->key() != Qt::Key_X && keyEvent->key() != Qt::Key_C && keyEvent->key() != Qt::Key_V && keyEvent->key() != Qt::Key_A) return true; } } #endif return QObject::eventFilter(obj, event); } #pragma mark === Engine === Engine::Engine() : QObject() { } Engine::Engine(const QString& name, const QString& program, const QStringList arguments, bool showPdf) : QObject(), f_name(name), f_program(program), f_arguments(arguments), f_showPdf(showPdf) { } Engine::Engine(const Engine& orig) : QObject(), f_name(orig.f_name), f_program(orig.f_program), f_arguments(orig.f_arguments), f_showPdf(orig.f_showPdf) { } Engine& Engine::operator=(const Engine& rhs) { f_name = rhs.f_name; f_program = rhs.f_program; f_arguments = rhs.f_arguments; f_showPdf = rhs.f_showPdf; return *this; } const QString Engine::name() const { return f_name; } const QString Engine::program() const { return f_program; } const QStringList Engine::arguments() const { return f_arguments; } bool Engine::showPdf() const { return f_showPdf; } void Engine::setName(const QString& name) { f_name = name; } void Engine::setProgram(const QString& program) { f_program = program; } void Engine::setArguments(const QStringList& arguments) { f_arguments = arguments; } void Engine::setShowPdf(bool showPdf) { f_showPdf = showPdf; }