2014-04-16 16:18:28 +00:00
|
|
|
#ifndef __FULLTEXTSEARCH_HH_INCLUDED__
|
|
|
|
#define __FULLTEXTSEARCH_HH_INCLUDED__
|
|
|
|
|
2023-05-30 06:31:07 +00:00
|
|
|
#include <QAbstractListModel>
|
|
|
|
#include <QAction>
|
|
|
|
#include <QList>
|
|
|
|
#include <QTimer>
|
|
|
|
#include <QThread>
|
|
|
|
#include <QRunnable>
|
2014-04-16 16:18:28 +00:00
|
|
|
#include <QSemaphore>
|
|
|
|
#include <QStringList>
|
2023-05-30 06:31:07 +00:00
|
|
|
|
2024-06-22 12:01:52 +00:00
|
|
|
#include <QRegularExpression>
|
2014-04-16 16:18:28 +00:00
|
|
|
|
2023-04-17 20:55:34 +00:00
|
|
|
#include "dict/dictionary.hh"
|
2014-04-16 16:18:28 +00:00
|
|
|
#include "ui_fulltextsearch.h"
|
2023-05-29 13:56:04 +00:00
|
|
|
|
2014-04-16 16:18:28 +00:00
|
|
|
#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
|
2014-04-22 13:47:02 +00:00
|
|
|
MaxDictionarySizeForFastSearch = 150000,
|
|
|
|
|
|
|
|
// Maxumum match length for highlight search results
|
2021-07-06 13:01:50 +00:00
|
|
|
// (QWebEnginePage::findText() crashes on too long strings)
|
2014-04-22 13:47:02 +00:00
|
|
|
MaxMatchLengthForHighlightResults = 500
|
2014-04-16 16:18:28 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
enum SearchMode {
|
2023-07-01 15:39:23 +00:00
|
|
|
WholeWords = 0, // aka Default search using Xapian query syntax
|
2014-04-16 16:18:28 +00:00
|
|
|
PlainText,
|
|
|
|
Wildcards,
|
|
|
|
RegExp
|
|
|
|
};
|
|
|
|
|
|
|
|
struct FtsHeadword
|
|
|
|
{
|
|
|
|
QString headword;
|
|
|
|
QStringList dictIDs;
|
2017-07-25 15:28:29 +00:00
|
|
|
QStringList foundHiliteRegExps;
|
|
|
|
bool matchCase;
|
|
|
|
|
|
|
|
FtsHeadword( QString const & headword_, QString const & dictid_, QStringList hilites, bool match_case ):
|
|
|
|
headword( headword_ ),
|
|
|
|
foundHiliteRegExps( hilites ),
|
|
|
|
matchCase( match_case )
|
2014-04-16 16:18:28 +00:00
|
|
|
{
|
|
|
|
dictIDs.append( dictid_ );
|
|
|
|
}
|
|
|
|
|
2017-06-08 16:15:56 +00:00
|
|
|
QString trimQuotes( QString const & ) const;
|
|
|
|
|
|
|
|
bool operator<( FtsHeadword const & other ) const;
|
2014-04-16 16:18:28 +00:00
|
|
|
|
|
|
|
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;
|
2022-10-06 11:32:45 +00:00
|
|
|
QTimer * timer;
|
2022-10-06 12:02:14 +00:00
|
|
|
QThread * timerThread;
|
2014-04-16 16:18:28 +00:00
|
|
|
|
|
|
|
public:
|
2023-06-16 00:37:33 +00:00
|
|
|
Indexing( QAtomicInt & cancelled, std::vector< sptr< Dictionary::Class > > const & dicts, QSemaphore & hasExited_ ):
|
2014-04-16 16:18:28 +00:00
|
|
|
isCancelled( cancelled ),
|
|
|
|
dictionaries( dicts ),
|
2022-10-06 11:32:45 +00:00
|
|
|
hasExited( hasExited_ ),
|
2023-06-16 00:37:33 +00:00
|
|
|
timer( new QTimer( nullptr ) ), // must be null since it will live in separate thread
|
|
|
|
timerThread( new QThread( this ) )
|
2022-10-06 11:32:45 +00:00
|
|
|
{
|
2023-06-16 00:37:33 +00:00
|
|
|
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 );
|
|
|
|
connect( timerThread, &QThread::finished, timer, &QObject::deleteLater );
|
2022-10-06 11:32:45 +00:00
|
|
|
}
|
2014-04-16 16:18:28 +00:00
|
|
|
|
|
|
|
~Indexing()
|
|
|
|
{
|
2022-10-06 11:32:45 +00:00
|
|
|
emit sendNowIndexingName( QString() );
|
2014-04-16 16:18:28 +00:00
|
|
|
hasExited.release();
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual void run();
|
|
|
|
|
|
|
|
signals:
|
|
|
|
void sendNowIndexingName( QString );
|
2022-10-06 11:32:45 +00:00
|
|
|
|
|
|
|
private slots:
|
|
|
|
void timeout();
|
2014-04-16 16:18:28 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
class FtsIndexing: public QObject
|
|
|
|
{
|
|
|
|
Q_OBJECT
|
2023-07-20 08:02:22 +00:00
|
|
|
|
2014-04-16 16:18:28 +00:00
|
|
|
public:
|
|
|
|
FtsIndexing( std::vector< sptr< Dictionary::Class > > const & dicts );
|
|
|
|
virtual ~FtsIndexing()
|
|
|
|
{
|
|
|
|
stopIndexing();
|
|
|
|
}
|
|
|
|
|
2014-04-17 14:18:15 +00:00
|
|
|
void setDictionaries( std::vector< sptr< Dictionary::Class > > const & dicts )
|
|
|
|
{
|
|
|
|
clearDictionaries();
|
|
|
|
dictionaries = dicts;
|
|
|
|
}
|
|
|
|
|
|
|
|
void clearDictionaries()
|
2023-06-13 00:07:54 +00:00
|
|
|
{
|
|
|
|
dictionaries.clear();
|
|
|
|
}
|
2014-04-17 14:18:15 +00:00
|
|
|
|
2014-04-16 16:18:28 +00:00
|
|
|
/// Start dictionaries indexing for full-text search
|
|
|
|
void doIndexing();
|
|
|
|
|
|
|
|
/// Break indexing thread
|
|
|
|
void stopIndexing();
|
|
|
|
|
|
|
|
QString nowIndexingName();
|
|
|
|
|
2023-06-13 00:07:54 +00:00
|
|
|
private:
|
2014-04-16 16:18:28 +00:00
|
|
|
QAtomicInt isCancelled;
|
|
|
|
QSemaphore indexingExited;
|
2014-04-17 14:18:15 +00:00
|
|
|
std::vector< sptr< Dictionary::Class > > dictionaries;
|
2014-04-16 16:18:28 +00:00
|
|
|
bool started;
|
|
|
|
QString nowIndexing;
|
2023-05-29 13:56:04 +00:00
|
|
|
QMutex nameMutex;
|
2014-04-16 16:18:28 +00:00
|
|
|
|
|
|
|
private slots:
|
2023-05-28 16:01:21 +00:00
|
|
|
void setNowIndexedName( const QString & name );
|
2014-04-16 16:18:28 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
std::list< sptr< Dictionary::DataRequest > > searchReqs;
|
|
|
|
|
|
|
|
FtsIndexing & ftsIdx;
|
2014-04-22 13:47:02 +00:00
|
|
|
|
2024-06-22 12:01:52 +00:00
|
|
|
QRegularExpression searchRegExp;
|
2022-10-07 01:59:41 +00:00
|
|
|
int matchedCount;
|
2014-04-23 14:19:46 +00:00
|
|
|
|
2014-04-16 16:18:28 +00:00
|
|
|
public:
|
|
|
|
FullTextSearchDialog( QWidget * parent,
|
|
|
|
Config::Class & cfg_,
|
|
|
|
std::vector< sptr< Dictionary::Class > > const & dictionaries_,
|
|
|
|
std::vector< Instances::Group > const & groups_,
|
|
|
|
FtsIndexing & ftsidx );
|
|
|
|
virtual ~FullTextSearchDialog();
|
|
|
|
|
2023-04-20 08:56:14 +00:00
|
|
|
void setSearchText( const QString & text );
|
|
|
|
|
2014-04-16 16:18:28 +00:00
|
|
|
void setCurrentGroup( unsigned group_ )
|
|
|
|
{
|
|
|
|
group = group_;
|
|
|
|
updateDictionaries();
|
|
|
|
}
|
|
|
|
|
|
|
|
void stopSearch();
|
|
|
|
|
2014-04-23 13:47:56 +00:00
|
|
|
protected:
|
|
|
|
bool eventFilter( QObject * obj, QEvent * ev );
|
|
|
|
|
2014-04-16 16:18:28 +00:00
|
|
|
private:
|
|
|
|
Ui::FullTextSearchDialog ui;
|
|
|
|
QList< FtsHeadword > results;
|
|
|
|
HeadwordsListModel * model;
|
|
|
|
WordListItemDelegate * delegate;
|
2014-06-24 13:55:06 +00:00
|
|
|
QAction helpAction;
|
2014-04-16 16:18:28 +00:00
|
|
|
|
|
|
|
void showDictNumbers();
|
|
|
|
|
|
|
|
private slots:
|
|
|
|
void setNewIndexingName( QString );
|
|
|
|
void saveData();
|
|
|
|
void accept();
|
|
|
|
void searchReqFinished();
|
2022-10-07 01:59:41 +00:00
|
|
|
void matchCount( int );
|
2014-04-16 16:18:28 +00:00
|
|
|
void reject();
|
|
|
|
void itemClicked( QModelIndex const & idx );
|
|
|
|
void updateDictionaries();
|
|
|
|
|
|
|
|
signals:
|
2014-04-22 13:47:02 +00:00
|
|
|
void showTranslationFor( QString const &,
|
|
|
|
QStringList const & dictIDs,
|
2024-06-22 12:01:52 +00:00
|
|
|
QRegularExpression const & searchRegExp,
|
2018-04-10 14:49:52 +00:00
|
|
|
bool ignoreDiacritics );
|
2014-04-16 16:18:28 +00:00
|
|
|
void closeDialog();
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
} // namespace FTS
|
|
|
|
|
|
|
|
#endif // __FULLTEXTSEARCH_HH_INCLUDED__
|