1515#include < QPushButton>
1616#include < QProcess>
1717#include < QSettings>
18+ #include < QSignalBlocker>
1819#include < QStatusBar>
1920#include < QTabWidget>
2021#include < QTextDocumentFragment>
@@ -83,6 +84,10 @@ MainWindow::MainWindow(const QMap<QString, QString> &versions, QWidget *parent)
8384 m_ActionViewProject->setChecked (m_DockProject->isVisible ());
8485 m_ActionViewFiles->setChecked (m_DockFiles->isVisible ());
8586 m_ActionViewConsole->setChecked (m_DockConsole->isVisible ());
87+
88+ // Ensure main window gets focus instead of search boxes
89+ setFocus ();
90+
8691 QTimer::singleShot (100 , [=] {
8792 QSettings settings;
8893 const QStringList files = settings.value (" open_files" ).toStringList ();
@@ -201,22 +206,45 @@ QDockWidget *MainWindow::buildConsoleDock()
201206QDockWidget *MainWindow::buildFilesDock ()
202207{
203208 auto dock = new QDockWidget (tr (" Files" ), this );
209+ auto widget = new QWidget (this );
210+ auto layout = new QVBoxLayout (widget);
211+ layout->setContentsMargins (0 , 0 , 0 , 0 );
212+ layout->setSpacing (2 );
213+
214+ m_SearchFiles = new QLineEdit (this );
215+ m_SearchFiles->setPlaceholderText (tr (" Search in open files..." ));
216+ m_SearchFiles->setClearButtonEnabled (true );
217+ connect (m_SearchFiles, &QLineEdit::textChanged, this , &MainWindow::handleFilesSearchChanged);
218+ layout->addWidget (m_SearchFiles);
219+
204220 m_ListOpenFiles = new QListView (this );
205221 m_ListOpenFiles->setContextMenuPolicy (Qt::CustomContextMenu);
206222 m_ListOpenFiles->setEditTriggers (QAbstractItemView::NoEditTriggers);
207223 m_ListOpenFiles->setMinimumWidth (240 );
208- m_ListOpenFiles->setModel (m_ModelOpenFiles = new QStandardItemModel (m_ListOpenFiles));
224+ m_ModelOpenFiles = new QStandardItemModel (m_ListOpenFiles);
225+ m_FilesProxyModel = new QSortFilterProxyModel (this );
226+ m_FilesProxyModel->setSourceModel (m_ModelOpenFiles);
227+ m_FilesProxyModel->setFilterCaseSensitivity (Qt::CaseInsensitive);
228+ m_FilesProxyModel->setFilterRole (Qt::DisplayRole);
229+ m_ListOpenFiles->setModel (m_FilesProxyModel);
209230 m_ListOpenFiles->setSelectionBehavior (QAbstractItemView::SelectItems);
210231 m_ListOpenFiles->setSelectionMode (QAbstractItemView::SingleSelection);
211- connect (m_ListOpenFiles->selectionModel (), &QItemSelectionModel::selectionChanged, this , &MainWindow::handleFilesSelectionChanged);
232+ // Get selection model after model is set to ensure it uses proxy model indices
233+ QItemSelectionModel *selectionModel = m_ListOpenFiles->selectionModel ();
234+ if (selectionModel) {
235+ connect (selectionModel, &QItemSelectionModel::selectionChanged, this , &MainWindow::handleFilesSelectionChanged);
236+ }
237+ layout->addWidget (m_ListOpenFiles);
238+
239+ widget->setLayout (layout);
212240 dock->setObjectName (" FilesDock" );
213- dock->setWidget (m_ListOpenFiles );
241+ dock->setWidget (widget );
214242 return dock;
215243}
216244
217245QToolBar *MainWindow::buildMainToolBar ()
218246{
219- auto toolbar = new QToolBar (this );
247+ auto toolbar = new QToolBar (tr ( " Sidebar " ), this );
220248 toolbar->addAction (QIcon (" :/icons/icons8/icons8-android-os-48.png" ), tr (" Open APK" ), this , &MainWindow::handleActionApk);
221249 toolbar->addAction (QIcon (" :/icons/icons8/icons8-folder-48.png" ), tr (" Open folder" ), this , &MainWindow::handleActionFolder);
222250 toolbar->addSeparator ();
@@ -313,6 +341,17 @@ QMenuBar *MainWindow::buildMenuBar()
313341QDockWidget *MainWindow::buildProjectsDock ()
314342{
315343 auto dock = new QDockWidget (tr (" Projects" ), this );
344+ auto widget = new QWidget (this );
345+ auto layout = new QVBoxLayout (widget);
346+ layout->setContentsMargins (0 , 0 , 0 , 0 );
347+ layout->setSpacing (2 );
348+
349+ m_SearchProjects = new QLineEdit (this );
350+ m_SearchProjects->setPlaceholderText (tr (" Search in project..." ));
351+ m_SearchProjects->setClearButtonEnabled (true );
352+ connect (m_SearchProjects, &QLineEdit::textChanged, this , &MainWindow::handleProjectsSearchChanged);
353+ layout->addWidget (m_SearchProjects);
354+
316355 m_ProjectsTree = new QTreeWidget (this );
317356 m_ProjectsTree->header ()->hide ();
318357 m_ProjectsTree->setContextMenuPolicy (Qt::CustomContextMenu);
@@ -324,8 +363,11 @@ QDockWidget *MainWindow::buildProjectsDock()
324363 connect (m_ProjectsTree, &QTreeWidget::customContextMenuRequested, this , &MainWindow::handleTreeContextMenu);
325364 connect (m_ProjectsTree, &QTreeWidget::doubleClicked, this , &MainWindow::handleTreeDoubleClicked);
326365 connect (m_ProjectsTree->selectionModel (), &QItemSelectionModel::selectionChanged, this , &MainWindow::handleTreeSelectionChanged);
366+ layout->addWidget (m_ProjectsTree);
367+
368+ widget->setLayout (layout);
327369 dock->setObjectName (" ProjectsDock" );
328- dock->setWidget (m_ProjectsTree );
370+ dock->setWidget (widget );
329371 return dock;
330372}
331373
@@ -834,10 +876,86 @@ void MainWindow::handleDecompileProgress(const int percent, const QString &messa
834876void MainWindow::handleFilesSelectionChanged (const QItemSelection &selected, const QItemSelection &deselected)
835877{
836878 Q_UNUSED (deselected)
837- if (!selected.isEmpty ()) {
838- auto index = selected.indexes ().first ();
839- const QString path = index.data (Qt::UserRole + 1 ).toString ();
840- m_TabEditors->setCurrentIndex (findTabIndex (path));
879+ if (selected.isEmpty () || !m_FilesProxyModel || !m_ModelOpenFiles) {
880+ return ;
881+ }
882+
883+ auto proxyIndex = selected.indexes ().first ();
884+ if (!proxyIndex.isValid ()) {
885+ return ;
886+ }
887+
888+ // Verify the index belongs to the proxy model - this is critical to prevent the assertion
889+ const QAbstractItemModel *indexModel = proxyIndex.model ();
890+ #ifdef QT_DEBUG
891+ if (indexModel != m_FilesProxyModel) {
892+ qDebug () << " Warning: Selection index from wrong model. Expected:" << m_FilesProxyModel << " Got:" << indexModel;
893+ return ;
894+ }
895+ #else
896+ if (!indexModel || indexModel != m_FilesProxyModel) {
897+ return ;
898+ }
899+ #endif
900+
901+ // Additional safety check: verify the proxy model is still valid
902+ if (!m_FilesProxyModel->sourceModel () || m_FilesProxyModel->sourceModel () != m_ModelOpenFiles) {
903+ #ifdef QT_DEBUG
904+ qDebug () << " Warning: Proxy model source mismatch" ;
905+ #endif
906+ return ;
907+ }
908+
909+ auto sourceIndex = m_FilesProxyModel->mapToSource (proxyIndex);
910+ if (sourceIndex.isValid () && sourceIndex.model () == m_ModelOpenFiles) {
911+ const QString path = m_ModelOpenFiles->data (sourceIndex, Qt::UserRole + 1 ).toString ();
912+ if (!path.isEmpty ()) {
913+ m_TabEditors->setCurrentIndex (findTabIndex (path));
914+ }
915+ }
916+ }
917+
918+ void MainWindow::handleFilesSearchChanged (const QString &text)
919+ {
920+ m_FilesProxyModel->setFilterFixedString (text);
921+ }
922+
923+ void MainWindow::handleProjectsSearchChanged (const QString &text)
924+ {
925+ for (int i = 0 ; i < m_ProjectsTree->topLevelItemCount (); ++i) {
926+ filterProjectTreeItems (m_ProjectsTree->topLevelItem (i), text);
927+ }
928+ }
929+
930+ void MainWindow::filterProjectTreeItems (QTreeWidgetItem *item, const QString &filter)
931+ {
932+ if (!item) {
933+ return ;
934+ }
935+
936+ bool visible = false ;
937+ QString itemText = item->text (0 );
938+
939+ // Check if this item matches the filter
940+ if (filter.isEmpty () || itemText.contains (filter, Qt::CaseInsensitive)) {
941+ visible = true ;
942+ }
943+
944+ // Check children recursively
945+ for (int i = 0 ; i < item->childCount (); ++i) {
946+ QTreeWidgetItem *child = item->child (i);
947+ filterProjectTreeItems (child, filter);
948+ // If any child is visible, this item should be visible too
949+ if (!child->isHidden ()) {
950+ visible = true ;
951+ }
952+ }
953+
954+ item->setHidden (!visible);
955+
956+ // Expand parent if this item is visible
957+ if (visible && item->parent ()) {
958+ item->parent ()->setExpanded (true );
841959 }
842960}
843961
@@ -949,12 +1067,23 @@ void MainWindow::handleTabChanged(const int index)
9491067 } else if (viewer) {
9501068 path = viewer->filePath ();
9511069 }
952- const int total = m_ModelOpenFiles->rowCount ();
953- for (int i = 0 ; i < total; ++i) {
954- const QModelIndex &mindex = m_ModelOpenFiles->index (i, 0 );
955- if (QString::compare (mindex.data (Qt::UserRole + 1 ).toString (), path) == 0 ) {
956- m_ListOpenFiles->setCurrentIndex (mindex);
957- break ;
1070+ // Block signals to prevent selection change handler from firing
1071+ QSignalBlocker blocker (m_ListOpenFiles);
1072+ if (m_ListOpenFiles->selectionModel ()) {
1073+ QSignalBlocker selectionBlocker (m_ListOpenFiles->selectionModel ());
1074+ const int total = m_ModelOpenFiles->rowCount ();
1075+ for (int i = 0 ; i < total; ++i) {
1076+ const QModelIndex &sourceIndex = m_ModelOpenFiles->index (i, 0 );
1077+ if (QString::compare (sourceIndex.data (Qt::UserRole + 1 ).toString (), path) == 0 ) {
1078+ // Map source index to proxy index before setting selection
1079+ if (m_FilesProxyModel) {
1080+ QModelIndex proxyIndex = m_FilesProxyModel->mapFromSource (sourceIndex);
1081+ if (proxyIndex.isValid ()) {
1082+ m_ListOpenFiles->setCurrentIndex (proxyIndex);
1083+ }
1084+ }
1085+ break ;
1086+ }
9581087 }
9591088 }
9601089 m_ActionClose->setEnabled (index >= 0 );
@@ -1009,12 +1138,26 @@ void MainWindow::handleTabCloseRequested(const int index)
10091138 } else if (viewer) {
10101139 path = viewer->filePath ();
10111140 }
1012- const int total = m_ModelOpenFiles->rowCount ();
1013- for (int i = 0 ; i < total; ++i) {
1014- const QModelIndex &mindex = m_ModelOpenFiles->index (i, 0 );
1015- if (QString::compare (mindex.data (Qt::UserRole + 1 ).toString (), path) == 0 ) {
1016- m_ModelOpenFiles->removeRow (mindex.row ());
1017- break ;
1141+ // Block signals during model update to prevent selection change errors
1142+ QSignalBlocker blocker (m_ListOpenFiles);
1143+ if (m_ListOpenFiles->selectionModel ()) {
1144+ QSignalBlocker selectionBlocker (m_ListOpenFiles->selectionModel ());
1145+ const int total = m_ModelOpenFiles->rowCount ();
1146+ for (int i = 0 ; i < total; ++i) {
1147+ const QModelIndex &mindex = m_ModelOpenFiles->index (i, 0 );
1148+ if (QString::compare (mindex.data (Qt::UserRole + 1 ).toString (), path) == 0 ) {
1149+ m_ModelOpenFiles->removeRow (mindex.row ());
1150+ break ;
1151+ }
1152+ }
1153+ } else {
1154+ const int total = m_ModelOpenFiles->rowCount ();
1155+ for (int i = 0 ; i < total; ++i) {
1156+ const QModelIndex &mindex = m_ModelOpenFiles->index (i, 0 );
1157+ if (QString::compare (mindex.data (Qt::UserRole + 1 ).toString (), path) == 0 ) {
1158+ m_ModelOpenFiles->removeRow (mindex.row ());
1159+ break ;
1160+ }
10181161 }
10191162 }
10201163 m_ActionUndo->setEnabled (false );
@@ -1025,6 +1168,13 @@ void MainWindow::handleTabCloseRequested(const int index)
10251168 m_TabEditors->removeTab (index);
10261169 if (m_TabEditors->count () == 0 ) {
10271170 m_CentralStack->setCurrentIndex (0 );
1171+ // Clear search boxes when all tabs are closed
1172+ if (m_SearchFiles) {
1173+ m_SearchFiles->clear ();
1174+ }
1175+ if (m_SearchProjects) {
1176+ m_SearchProjects->clear ();
1177+ }
10281178 }
10291179}
10301180
@@ -1168,7 +1318,15 @@ void MainWindow::openFile(const QString &path)
11681318 const QIcon icon = m_FileIconProvider.icon (info);
11691319 auto item = new QStandardItem (icon, info.fileName ());
11701320 item->setData (path, Qt::UserRole + 1 );
1171- m_ModelOpenFiles->appendRow (item);
1321+
1322+ // Block signals during model update to prevent selection change errors
1323+ QSignalBlocker blocker (m_ListOpenFiles);
1324+ if (m_ListOpenFiles->selectionModel ()) {
1325+ QSignalBlocker selectionBlocker (m_ListOpenFiles->selectionModel ());
1326+ m_ModelOpenFiles->appendRow (item);
1327+ } else {
1328+ m_ModelOpenFiles->appendRow (item);
1329+ }
11721330 const int i = m_TabEditors->addTab (widget, icon, info.fileName ());
11731331 m_TabEditors->setCurrentIndex (i);
11741332 m_TabEditors->setTabToolTip (i, path);
@@ -1194,6 +1352,14 @@ void MainWindow::openFindReplaceDialog(QPlainTextEdit *edit, const bool replace)
11941352
11951353void MainWindow::openProject (const QString &folder, const bool last)
11961354{
1355+ // Clear search boxes when opening a new project
1356+ if (m_SearchFiles) {
1357+ m_SearchFiles->clear ();
1358+ }
1359+ if (m_SearchProjects) {
1360+ m_SearchProjects->clear ();
1361+ }
1362+
11971363 QSettings settings;
11981364 settings.setValue (" open_project" , folder);
11991365 settings.sync ();
0 commit comments