@@ -800,7 +800,7 @@ class HelpTab : public QWidget
800800
801801 <h2 style="font-size: 1.2em; font-weight: 600; color: #34495e;">Tabs Overview</h2>
802802 <ul style="padding-left: 20px; list-style-type: disc;">
803- <li><b>Learn Words:</b> Import text files (.txt, .html ) with Devanagari. Extracts valid words into your dictionary (increments frequency for existing words).</li>
803+ <li><b>Learn Words:</b> Import text files (.txt) with Devanagari texts . Extracts valid words into your dictionary (increments frequency for existing words).</li>
804804 <li><b>Edit Dictionary:</b> View, add, delete, or reset your personal word database.</li>
805805 <li><b>Settings:</b> Configure & test transliteration engine settings.</li>
806806 <li><b>Help:</b> This guide + download pre-trained database.</li>
@@ -828,20 +828,25 @@ class HelpTab : public QWidget
828828 </div>
829829 )" );
830830 helpContent_->setSizePolicy (QSizePolicy::Expanding, QSizePolicy::Expanding);
831+ helpContent_->setVerticalScrollBarPolicy (Qt::ScrollBarAlwaysOn);
831832 mainLay->addWidget (helpContent_, 1 );
832833 dwninfo = new QLabel (" If you want a head start, you can download a dictionary with pre trained common words." );
833834 dwninfo->setWordWrap (true );
835+ dwninfo->setStyleSheet (" color: red;" );
834836 auto *group = new QGroupBox (" " );
835837 auto *groupLay = new QVBoxLayout (group);
836838 groupLay->addWidget (dwninfo);
837839 downloadBtn_ = new QPushButton (" Download and Replace Database" );
840+ stopDownloadBtn_ = new QPushButton (" Stop Download" );
841+ stopDownloadBtn_->setVisible (false );
838842 log_ = new QPlainTextEdit;
839843 log_->setReadOnly (true );
840844 log_->setMaximumHeight (80 );
841845 log_->setPlaceholderText (" Log output will appear here..." );
842846
843847 auto *btnLay = new QHBoxLayout;
844848 btnLay->addWidget (downloadBtn_);
849+ btnLay->addWidget (stopDownloadBtn_);
845850 btnLay->addStretch ();
846851
847852 groupLay->addLayout (btnLay);
@@ -850,11 +855,44 @@ class HelpTab : public QWidget
850855
851856 netManager_ = new QNetworkAccessManager (this );
852857 connect (downloadBtn_, &QPushButton::clicked, this , &HelpTab::downloadDatabase);
858+ connect (stopDownloadBtn_, &QPushButton::clicked, this , &HelpTab::stopDownload);
859+
860+ m_downloadTimer = new QTimer (this );
861+ m_downloadTimer->setSingleShot (true );
862+ connect (m_downloadTimer, &QTimer::timeout, this , &HelpTab::onDownloadTimeout);
853863 }
854864
865+ void setOnDatabaseUpdateCallback (std::function<void ()> callback) {
866+ onDatabaseUpdate_ = callback;
867+ }
855868
856869private:
870+ void onDownloadTimeout () {
871+ if (m_currentReply) {
872+ log_->appendPlainText (" Download timed out (no activity for 15 seconds)." );
873+ m_currentReply->abort ();
874+ }
875+ }
876+
877+ void stopDownload () {
878+ if (m_currentReply) {
879+ m_downloadTimer->stop ();
880+ m_userStopped = true ;
881+ m_currentReply->abort ();
882+ }
883+ }
884+
857885 QLabel *dwninfo;
886+ QTextEdit *helpContent_;
887+ QPushButton *downloadBtn_;
888+ QPushButton *stopDownloadBtn_;
889+ QPlainTextEdit *log_;
890+ QNetworkAccessManager *netManager_;
891+ QNetworkReply *m_currentReply = nullptr ;
892+ std::function<void ()> onDatabaseUpdate_ = nullptr ;
893+ QTimer *m_downloadTimer = nullptr ;
894+ bool m_userStopped = false ;
895+
858896 void downloadDatabase ()
859897 {
860898 const QString warningText =
@@ -878,61 +916,110 @@ class HelpTab : public QWidget
878916 log_->clear ();
879917 log_->appendPlainText (" Looking for fresh dictionary…" );
880918 downloadBtn_->setEnabled (false );
919+ stopDownloadBtn_->setVisible (true );
920+ m_userStopped = false ;
881921
882922 QString dirPath = QStandardPaths::writableLocation (QStandardPaths::GenericDataLocation)
883923 + QLatin1String (" /fcitx5-lekhika" );
884924 QDir dir (dirPath);
885925 if (!dir.exists () && !dir.mkpath (" ." )) {
886926 log_->appendPlainText (" Error: could not create " + dirPath);
887927 downloadBtn_->setEnabled (true );
928+ stopDownloadBtn_->setVisible (false );
888929 return ;
889930 }
890931
891932 const QString localFile = dirPath + QLatin1String (" /lekhikadict.akshardb" );
892933 const QUrl url (" https://github.com/khumnath/fcitx5-lekhika/releases/download/dictionary/lekhikadict.akshardb" );
893934 QNetworkRequest req (url);
894- QNetworkReply *netReply = netManager_->get (req);
895-
896- connect (netReply, &QNetworkReply::finished, this , [this , netReply, localFile]() {
897- if (netReply->error () != QNetworkReply::NoError) {
898- QString detail;
899- if (netReply->error () == QNetworkReply::ContentNotFoundError)
900- detail = " Server replied: 404 – dictionary not found. database not changed." ;
901- else if (netReply->error () == QNetworkReply::HostNotFoundError ||
902- netReply->error () == QNetworkReply::TimeoutError)
903- detail = " No internet connection." ;
904- else
905- detail = QString (" Network error: %1" ).arg (netReply->errorString ());
906-
907- log_->appendPlainText (" Download failed. " + detail);
908- } else {
909- QString tempFile = localFile + " .tmp" ;
910- QFile file (tempFile);
911- if (!file.open (QIODevice::WriteOnly)) {
912- log_->appendPlainText (QString (" Error: cannot write temporary file %1" ).arg (tempFile));
913- } else {
914- file.write (netReply->readAll ());
915- file.close ();
916-
917- QFile::remove (localFile);
918- if (QFile::rename (tempFile, localFile)) {
919- log_->appendPlainText (" Success! Dictionary updated." );
920- log_->appendPlainText (" Please restart fcitx5 to use the new dictionary. dictionary can be tested on settings tab without restart this application. " );
935+ m_currentReply = netManager_->get (req);
936+ m_downloadTimer->start (15000 ); // 15 second timeout
937+
938+ connect (m_currentReply, &QNetworkReply::downloadProgress, this , [this ](qint64 bytesReceived, qint64 bytesTotal) {
939+ m_downloadTimer->start (15000 ); // Reset timer on progress
940+ if (bytesTotal > 0 ) {
941+ QString progressText = QString::asprintf (" Downloading: %.2f MB / %.2f MB" ,
942+ bytesReceived / (1024.0 * 1024.0 ),
943+ bytesTotal / (1024.0 * 1024.0 ));
944+ QMetaObject::invokeMethod (log_, [this , progressText]() {
945+ QTextCursor cursor = log_->textCursor ();
946+ cursor.movePosition (QTextCursor::End);
947+ cursor.select (QTextCursor::BlockUnderCursor);
948+ cursor.removeSelectedText ();
949+ if (log_->toPlainText ().endsWith (' \n ' )) {
950+ QTextCursor cleanupCursor = log_->textCursor ();
951+ cleanupCursor.movePosition (QTextCursor::End);
952+ cleanupCursor.deletePreviousChar ();
953+ }
954+ log_->appendPlainText (progressText);
955+ }, Qt::QueuedConnection);
956+ }
957+ });
958+
959+ connect (m_currentReply, &QNetworkReply::finished, this , [this , localFile]() {
960+ m_downloadTimer->stop ();
961+
962+ QNetworkReply::NetworkError error = m_currentReply->error ();
963+ QString errorString = m_currentReply->errorString ();
964+ QByteArray data;
965+ if (error == QNetworkReply::NoError) {
966+ data = m_currentReply->readAll ();
967+ }
968+
969+ m_currentReply->deleteLater ();
970+ m_currentReply = nullptr ;
971+
972+ QMetaObject::invokeMethod (this , [this , error, errorString, data, localFile]() {
973+ QTextCursor cursor = log_->textCursor ();
974+ cursor.movePosition (QTextCursor::End);
975+ cursor.select (QTextCursor::BlockUnderCursor);
976+ cursor.removeSelectedText ();
977+ if (!log_->toPlainText ().isEmpty () && !log_->toPlainText ().endsWith (' \n ' )) {
978+ QTextCursor cleanupCursor = log_->textCursor ();
979+ cleanupCursor.movePosition (QTextCursor::End);
980+ cleanupCursor.deletePreviousChar ();
981+ }
982+
983+ if (error == QNetworkReply::OperationCanceledError) {
984+ if (!m_userStopped) {
985+ } else {
986+ log_->appendPlainText (" Download cancelled by user." );
987+ }
988+ } else if (error != QNetworkReply::NoError) {
989+ QString detail;
990+ if (error == QNetworkReply::ContentNotFoundError)
991+ detail = " Server replied: 404 – dictionary not found. database not changed." ;
992+ else if (error == QNetworkReply::HostNotFoundError ||
993+ error == QNetworkReply::TimeoutError)
994+ detail = " No internet connection." ;
995+ else
996+ detail = QString (" Network error: %1" ).arg (errorString);
997+ log_->appendPlainText (" Download failed. " + detail);
921998 } else {
922- log_->appendPlainText (" Error: could not replace the old dictionary file." );
999+ QString tempFile = localFile + " .tmp" ;
1000+ QFile file (tempFile);
1001+ if (!file.open (QIODevice::WriteOnly)) {
1002+ log_->appendPlainText (QString (" Error: cannot write temporary file %1" ).arg (tempFile));
1003+ } else {
1004+ file.write (data);
1005+ file.close ();
1006+
1007+ QFile::remove (localFile);
1008+ if (QFile::rename (tempFile, localFile)) {
1009+ log_->appendPlainText (" Success! Dictionary updated." );
1010+ log_->appendPlainText (" Please restart fcitx5 to use the new dictionary. dictionary can be tested on settings tab without restart this application. " );
1011+ if (onDatabaseUpdate_) onDatabaseUpdate_ ();
1012+ } else {
1013+ log_->appendPlainText (" Error: could not replace the old dictionary file." );
1014+ }
1015+ }
9231016 }
924- }
925- }
9261017
927- downloadBtn_->setEnabled (true );
928- netReply->deleteLater ();
1018+ downloadBtn_->setEnabled (true );
1019+ stopDownloadBtn_->setVisible (false );
1020+ }, Qt::QueuedConnection);
9291021 });
9301022 }
931-
932- QTextEdit *helpContent_;
933- QPushButton *downloadBtn_;
934- QPlainTextEdit *log_;
935- QNetworkAccessManager *netManager_;
9361023};
9371024
9381025
@@ -956,33 +1043,44 @@ class MainWin : public QMainWindow
9561043 tab->setDocumentMode (true );
9571044 tabBar->setExpanding (true );
9581045
959- auto *importTab = new ImportTab;
960- auto *editTab = new DbEditorTab;
961- auto *testTab = new TestTab;
962- auto *helpTab = new HelpTab;
1046+ m_importTab = new ImportTab;
1047+ m_editTab = new DbEditorTab;
1048+ m_testTab = new TestTab;
1049+ m_helpTab = new HelpTab;
9631050
964- tab->addTab (importTab , " Learn Words" );
965- tab->addTab (editTab , " Edit Dictionary" );
966- tab->addTab (testTab , " Test" );
967- tab->addTab (helpTab , " Help" );
1051+ tab->addTab (m_importTab , " Learn Words" );
1052+ tab->addTab (m_editTab , " Edit Dictionary" );
1053+ tab->addTab (m_testTab , " Test" );
1054+ tab->addTab (m_helpTab , " Help" );
9681055
969- connect (tab, &QTabWidget::currentChanged, this , [tab, editTab ](int idx) {
970- if (tab->widget (idx) == editTab) editTab ->refresh ();
1056+ connect (tab, &QTabWidget::currentChanged, this , [this , tab ](int idx) {
1057+ if (tab->widget (idx) == m_editTab) m_editTab ->refresh ();
9711058 });
9721059
9731060 setCentralWidget (tab);
974- auto update_fn = [this ]() {
1061+
1062+ // Setup callbacks
1063+ auto update_status_fn = [this ]() {
9751064 this ->updateStatusBar ();
9761065 };
9771066
978- importTab->setOnDatabaseUpdateCallback (update_fn);
979- editTab->setOnDatabaseUpdateCallback (update_fn);
1067+ m_importTab->setOnDatabaseUpdateCallback (update_status_fn);
1068+ m_editTab->setOnDatabaseUpdateCallback (update_status_fn);
1069+
1070+ m_helpTab->setOnDatabaseUpdateCallback ([this ](){
1071+ m_editTab->refresh ();
1072+ this ->updateStatusBar ();
1073+ });
9801074
9811075 updateStatusBar ();
9821076 }
9831077
9841078
9851079private:
1080+ ImportTab* m_importTab;
1081+ DbEditorTab* m_editTab;
1082+ TestTab* m_testTab;
1083+ HelpTab* m_helpTab;
9861084 QWidget *m_statusWidget{nullptr };
9871085
9881086 void updateStatusBar () {
0 commit comments