goldendict-ng/fulltextsearch.hh
2022-10-11 22:09:15 +08:00

257 lines
6.1 KiB
C++

#ifndef __FULLTEXTSEARCH_HH_INCLUDED__
#define __FULLTEXTSEARCH_HH_INCLUDED__
#include <QSemaphore>
#include <QStringList>
#if (QT_VERSION >= QT_VERSION_CHECK(6,0,0))
#include <QtCore5Compat/QRegExp>
#else
#include <QRegExp>
#endif
#include <QAbstractListModel>
#include <QList>
#include <QAction>
#include "dictionary.hh"
#include "ui_fulltextsearch.h"
#include "mutex.hh"
#include "config.hh"
#include "instances.hh"
#include "delegate.hh"
namespace FTS
{
enum {
// Minimum word length for indexing
MinimumWordSize = 4,
// Maximum dictionary size for first iteration of FTS indexing
MaxDictionarySizeForFastSearch = 150000,
// Maxumum match length for highlight search results
// (QWebEnginePage::findText() crashes on too long strings)
MaxMatchLengthForHighlightResults = 500
};
enum SearchMode
{
WholeWords = 0,
PlainText,
Wildcards,
RegExp
};
struct FtsHeadword
{
QString headword;
QStringList dictIDs;
QStringList foundHiliteRegExps;
bool matchCase;
FtsHeadword( QString const & headword_, QString const & dictid_,
QStringList hilites, bool match_case ) :
headword( headword_ ),
foundHiliteRegExps( hilites ),
matchCase( match_case )
{
dictIDs.append( dictid_ );
}
QString trimQuotes( QString const & ) const;
bool operator <( FtsHeadword const & other ) const;
bool operator ==( FtsHeadword const & other ) const
{ return headword.compare( other.headword, Qt::CaseInsensitive ) == 0; }
bool operator !=( FtsHeadword const & other ) const
{ return headword.compare( other.headword, Qt::CaseInsensitive ) != 0; }
};
class Indexing : public QObject, public QRunnable
{
Q_OBJECT
QAtomicInt & isCancelled;
std::vector< sptr< Dictionary::Class > > const & dictionaries;
QSemaphore & hasExited;
QTimer * timer;
QThread * timerThread;
public:
Indexing( QAtomicInt & cancelled, std::vector< sptr< Dictionary::Class > > const & dicts,
QSemaphore & hasExited_):
isCancelled( cancelled ),
dictionaries( dicts ),
hasExited( hasExited_ ),
timer(new QTimer(0)),
timerThread(new QThread(this))
{
connect(timer, &QTimer::timeout, this, &Indexing::timeout);
timer->moveToThread(timerThread);
connect(timerThread, &QThread::started, timer, [this](){timer->start(2000);});
connect(timerThread, &QThread::finished, timer, &QTimer::stop);
}
~Indexing()
{
emit sendNowIndexingName( QString() );
hasExited.release();
}
virtual void run();
signals:
void sendNowIndexingName( QString );
private slots:
void timeout();
};
class FtsIndexing : public QObject
{
Q_OBJECT
public:
FtsIndexing( std::vector< sptr< Dictionary::Class > > const & dicts );
virtual ~FtsIndexing()
{ stopIndexing(); }
void setDictionaries( std::vector< sptr< Dictionary::Class > > const & dicts )
{
clearDictionaries();
dictionaries = dicts;
}
void clearDictionaries()
{ dictionaries.clear(); }
/// Start dictionaries indexing for full-text search
void doIndexing();
/// Break indexing thread
void stopIndexing();
QString nowIndexingName();
protected:
QAtomicInt isCancelled;
QSemaphore indexingExited;
std::vector< sptr< Dictionary::Class > > dictionaries;
bool started;
QString nowIndexing;
Mutex nameMutex;
private slots:
void setNowIndexedName( QString name );
signals:
void newIndexingName( QString name );
};
/// A model to be projected into the view, according to Qt's MVC model
class HeadwordsListModel: public QAbstractListModel
{
Q_OBJECT
public:
HeadwordsListModel( QWidget * parent, QList< FtsHeadword > & headwords_,
std::vector< sptr< Dictionary::Class > > const & dicts ):
QAbstractListModel( parent ), headwords( headwords_ ),
dictionaries( dicts )
{}
int rowCount( QModelIndex const & parent ) const;
QVariant data( QModelIndex const & index, int role ) const;
// bool insertRows( int row, int count, const QModelIndex & parent );
// bool removeRows( int row, int count, const QModelIndex & parent );
// bool setData( QModelIndex const & index, const QVariant & value, int role );
void addResults(const QModelIndex & parent, QList< FtsHeadword > const & headwords );
bool clear();
private:
QList< FtsHeadword > & headwords;
std::vector< sptr< Dictionary::Class > > const & dictionaries;
int getDictIndex( QString const & id ) const;
signals:
void contentChanged();
};
class FullTextSearchDialog : public QDialog
{
Q_OBJECT
Config::Class & cfg;
std::vector< sptr< Dictionary::Class > > const & dictionaries;
std::vector< Instances::Group > const & groups;
unsigned group;
std::vector< sptr< Dictionary::Class > > activeDicts;
bool ignoreWordsOrder;
bool ignoreDiacritics;
std::list< sptr< Dictionary::DataRequest > > searchReqs;
FtsIndexing & ftsIdx;
QRegExp searchRegExp;
int matchedCount;
public:
FullTextSearchDialog( QWidget * parent,
Config::Class & cfg_,
std::vector< sptr< Dictionary::Class > > const & dictionaries_,
std::vector< Instances::Group > const & groups_,
FtsIndexing & ftsidx );
virtual ~FullTextSearchDialog();
void setCurrentGroup( unsigned group_ )
{ group = group_; updateDictionaries(); }
void stopSearch();
protected:
bool eventFilter( QObject * obj, QEvent * ev );
private:
Ui::FullTextSearchDialog ui;
QList< FtsHeadword > results;
HeadwordsListModel * model;
WordListItemDelegate * delegate;
QAction helpAction;
void showDictNumbers();
private slots:
void setNewIndexingName( QString );
void saveData();
void accept();
void setLimitsUsing();
void ignoreWordsOrderClicked();
void ignoreDiacriticsClicked();
void searchReqFinished();
void matchCount(int);
void reject();
void itemClicked( QModelIndex const & idx );
void updateDictionaries();
void helpRequested();
signals:
void showTranslationFor( QString const &, QStringList const & dictIDs,
QRegExp const & searchRegExp, bool ignoreDiacritics );
void closeDialog();
};
} // namespace FTS
#endif // __FULLTEXTSEARCH_HH_INCLUDED__