• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IJHack / QtPass / 24588315042

17 Apr 2026 09:53PM UTC coverage: 21.778% (-0.04%) from 21.818%
24588315042

push

github

web-flow
fix: clear stale selection and update proxy filter on profile change (#1033)

When switching profiles or accepting config changes:

- `proxyModel.store` was never updated, so the filter (StoreModel::showThis)
  kept using the old profile's path — causing Util::getDir to compute
  `../../old-profile/subdir/` relative paths when adding new entries.
- Qt's delayed layout re-evaluation (doDelayedItemsLayout, triggered by
  setRootIndex) could reinstate a stale currentIndex, compounding the issue.
- Decrypted content, currentDir, clipboard text, passwordName, and the panel
  were not cleared, leaving stale data from the previous profile visible.

Fix:
- Add StoreModel::setStore() to update the filter path without reinitialising
  the source model.
- Call proxyModel.setStore() before mapFromSource so filtering is correct
  when the new root is mapped.
- Call deselect() to clear all stale UI state (currentDir, clipboard, panel,
  passwordName, edit/delete actions).
- Call ui->treeView->setCurrentIndex(QModelIndex()) after setRootIndex so
  the view's own currentIndexSet state is also cleared.
- Cache QtPassSettings::getPassStore() in a local variable to avoid
  redundant settings reads.
- Apply the same fixes to config() where the same staleness issues exist.

Closes #248

0 of 16 new or added lines in 2 files covered. (0.0%)

10 existing lines in 2 files now uncovered.

1176 of 5400 relevant lines covered (21.78%)

8.66 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

53.78
/src/storemodel.cpp
1
// SPDX-FileCopyrightText: 2014 Anne Jan Brouwer
2
// SPDX-License-Identifier: GPL-3.0-or-later
3
#include "storemodel.h"
4
#include "qtpasssettings.h"
5

6
#include "util.h"
7
#include <QDebug>
8
#include <QFileSystemModel>
9
#include <QMessageBox>
10
#include <QMimeData>
11
#include <QRegularExpression>
12
#include <utility>
13

14
auto operator<<(
1✔
15
    QDataStream &out,
16
    const dragAndDropInfoPasswordStore &dragAndDropInfoPasswordStore)
17
    -> QDataStream & {
18
  out << dragAndDropInfoPasswordStore.isDir
1✔
19
      << dragAndDropInfoPasswordStore.isFile
1✔
20
      << dragAndDropInfoPasswordStore.path;
1✔
21
  return out;
1✔
22
}
23

24
auto operator>>(QDataStream &in,
×
25
                dragAndDropInfoPasswordStore &dragAndDropInfoPasswordStore)
26
    -> QDataStream & {
27
  in >> dragAndDropInfoPasswordStore.isDir >>
×
28
      dragAndDropInfoPasswordStore.isFile >> dragAndDropInfoPasswordStore.path;
×
29
  return in;
×
30
}
31

32
/**
33
 * @brief StoreModel::StoreModel
34
 * SubClass of QSortFilterProxyModel via
35
 * http://www.qtcentre.org/threads/46471-QTreeView-Filter
36
 */
37
StoreModel::StoreModel() { fs = nullptr; }
19✔
38

39
/**
40
 * @brief StoreModel::filterAcceptsRow should row be shown, wrapper for
41
 * StoreModel::showThis method.
42
 * @param sourceRow
43
 * @param sourceParent
44
 * @return
45
 */
46
auto StoreModel::filterAcceptsRow(int sourceRow,
19✔
47
                                  const QModelIndex &sourceParent) const
48
    -> bool {
49
  QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
19✔
50
  return showThis(index);
19✔
51
}
52

53
/**
54
 * @brief StoreModel::showThis should a row be shown, based on our search
55
 * criteria.
56
 * @param index
57
 * @return
58
 */
59
auto StoreModel::showThis(const QModelIndex index) const -> bool {
44✔
60
  bool retVal = false;
61
  if (fs == nullptr) {
44✔
62
    return retVal;
63
  }
64
  // Gives you the info for number of childs with a parent
65
  if (sourceModel()->rowCount(index) > 0) {
43✔
66
    for (int nChild = 0; nChild < sourceModel()->rowCount(index); ++nChild) {
24✔
67
      QModelIndex childIndex = sourceModel()->index(nChild, 0, index);
24✔
68
      if (!childIndex.isValid()) {
69
        break;
70
      }
71
      retVal = showThis(childIndex);
24✔
72
      if (retVal) {
24✔
73
        break;
74
      }
75
    }
76
  } else {
77
    QModelIndex useIndex = sourceModel()->index(index.row(), 0, index.parent());
19✔
78
    QString path = fs->filePath(useIndex);
19✔
79
    path = QDir(store).relativeFilePath(path);
38✔
80
    if (path.startsWith(".git")) {
38✔
81
      return false;
82
    }
83
    path.replace(Util::endsWithGpg(), "");
18✔
84
    retVal = path.contains(filterRegularExpression());
18✔
85
  }
86
  return retVal;
87
}
88

89
/**
90
 * @brief StoreModel::setModelAndStore update the source model and store.
91
 * @param sourceModel
92
 * @param passStore
93
 */
94
void StoreModel::setModelAndStore(QFileSystemModel *sourceModel,
14✔
95
                                  QString passStore) {
96
  setSourceModel(sourceModel);
14✔
97
  fs = sourceModel;
14✔
98
  store = std::move(passStore);
99
}
14✔
100

NEW
101
void StoreModel::setStore(const QString &passStore) {
×
NEW
102
  store = passStore;
×
NEW
103
  invalidateFilter();
×
NEW
104
}
×
105

106
/**
107
 * @brief StoreModel::data don't show the .gpg at the end of a file.
108
 * @param index
109
 * @param role
110
 * @return
111
 */
112
auto StoreModel::data(const QModelIndex &index, int role) const -> QVariant {
2✔
113
  if (!index.isValid()) {
114
    return {};
115
  }
116

117
  QVariant initial_value;
118
  initial_value = QSortFilterProxyModel::data(index, role);
1✔
119

120
  if (role == Qt::DisplayRole) {
1✔
121
    QString name = initial_value.toString();
1✔
122
    name.replace(Util::endsWithGpg(), "");
1✔
123
    initial_value.setValue(name);
1✔
124
  }
125

126
  return initial_value;
1✔
127
}
1✔
128

129
/**
130
 * @brief StoreModel::supportedDropActions enable drop.
131
 * @return
132
 */
133
auto StoreModel::supportedDropActions() const -> Qt::DropActions {
1✔
134
  return Qt::CopyAction | Qt::MoveAction;
1✔
135
}
136

137
/**
138
 * @brief StoreModel::supportedDragActions enable drag.
139
 * @return
140
 */
141
auto StoreModel::supportedDragActions() const -> Qt::DropActions {
1✔
142
  return Qt::CopyAction | Qt::MoveAction;
1✔
143
}
144

145
/**
146
 * @brief StoreModel::flags
147
 * @param index
148
 * @return
149
 */
150
auto StoreModel::flags(const QModelIndex &index) const -> Qt::ItemFlags {
2✔
151
  Qt::ItemFlags defaultFlags = QSortFilterProxyModel::flags(index);
2✔
152

153
  if (index.isValid()) {
154
    return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
155
  }
156
  return Qt::ItemIsDropEnabled | defaultFlags;
1✔
157
}
158

159
/**
160
 * @brief StoreModel::mimeTypes
161
 * @return
162
 */
163
auto StoreModel::mimeTypes() const -> QStringList {
1✔
164
  QStringList types;
1✔
165
  types << "application/vnd+qtpass.dragAndDropInfoPasswordStore";
1✔
166
  return types;
1✔
167
}
168

169
/**
170
 * @brief StoreModel::mimeData
171
 * @param indexes
172
 * @return
173
 */
174
auto StoreModel::mimeData(const QModelIndexList &indexes) const -> QMimeData * {
1✔
175
  dragAndDropInfoPasswordStore info;
176

177
  QByteArray encodedData;
1✔
178
  // only use the first, otherwise we should enable multiselection
179
  QModelIndex index = indexes.at(0);
1✔
180
  if (index.isValid()) {
181
    QModelIndex useIndex = mapToSource(index);
1✔
182

183
    info.isDir = fs->fileInfo(useIndex).isDir();
1✔
184
    info.isFile = fs->fileInfo(useIndex).isFile();
1✔
185
    info.path = fs->fileInfo(useIndex).absoluteFilePath();
2✔
186
    QDataStream stream(&encodedData, QIODevice::WriteOnly);
1✔
187
    stream << info;
1✔
188
  }
1✔
189

190
  auto *mimeData = new QMimeData();
1✔
191
  mimeData->setData("application/vnd+qtpass.dragAndDropInfoPasswordStore",
2✔
192
                    encodedData);
193
  return mimeData;
1✔
194
}
195

196
/**
197
 * @brief StoreModel::canDropMimeData
198
 * @param data
199
 * @param action
200
 * @param row
201
 * @param column
202
 * @param parent
203
 * @return
204
 */
205
auto StoreModel::canDropMimeData(const QMimeData *data, Qt::DropAction action,
×
206
                                 int row, int column,
207
                                 const QModelIndex &parent) const -> bool {
208
#ifdef QT_DEBUG
209
  qDebug() << action << row;
210
#else
211
  Q_UNUSED(action)
212
  Q_UNUSED(row)
213
#endif
214

215
  QModelIndex useIndex =
216
      this->index(parent.row(), parent.column(), parent.parent());
×
217
  QByteArray encodedData =
218
      data->data("application/vnd+qtpass.dragAndDropInfoPasswordStore");
×
219
  QDataStream stream(&encodedData, QIODevice::ReadOnly);
×
220
  dragAndDropInfoPasswordStore info;
221
  stream >> info;
×
222
  if (!data->hasFormat("application/vnd+qtpass.dragAndDropInfoPasswordStore")) {
×
223
    return false;
224
  }
225

226
  if (column > 0) {
×
227
    return false;
228
  }
229

230
  // you can drop a folder on a folder
231
  if (fs->fileInfo(mapToSource(useIndex)).isDir() && info.isDir) {
×
232
    return true;
233
  }
234
  // you can drop a file on a folder
235
  if (fs->fileInfo(mapToSource(useIndex)).isDir() && info.isFile) {
×
236
    return true;
237
  }
238
  // you can drop a file on a file
239
  if (fs->fileInfo(mapToSource(useIndex)).isFile() && info.isFile) {
×
240
    return true;
241
  }
242

243
  return false;
244
}
×
245

246
/**
247
 * @brief StoreModel::dropMimeData
248
 * @param data
249
 * @param action
250
 * @param row
251
 * @param column
252
 * @param parent
253
 * @return
254
 */
255
auto StoreModel::dropMimeData(const QMimeData *data, Qt::DropAction action,
×
256
                              int row, int column, const QModelIndex &parent)
257
    -> bool {
258
  if (!canDropMimeData(data, action, row, column, parent)) {
×
259
    return false;
260
  }
261

262
  if (action == Qt::IgnoreAction) {
×
263
    return true;
264
  }
265
  QByteArray encodedData =
266
      data->data("application/vnd+qtpass.dragAndDropInfoPasswordStore");
×
267

268
  QDataStream stream(&encodedData, QIODevice::ReadOnly);
×
269
  dragAndDropInfoPasswordStore info;
270
  stream >> info;
×
271
  QModelIndex destIndex =
272
      this->index(parent.row(), parent.column(), parent.parent());
×
273
  QFileInfo destFileinfo = fs->fileInfo(mapToSource(destIndex));
×
274
  QFileInfo srcFileInfo = QFileInfo(info.path);
×
275
  QString cleanedSrc = QDir::cleanPath(srcFileInfo.absoluteFilePath());
×
276
  QString cleanedDest = QDir::cleanPath(destFileinfo.absoluteFilePath());
×
277
  if (info.isDir) {
×
278
    // dropped dir onto dir
279
    if (destFileinfo.isDir()) {
×
280
      QDir destDir = QDir(cleanedDest).filePath(srcFileInfo.fileName());
×
281
      QString cleanedDestDir = QDir::cleanPath(destDir.absolutePath());
×
282
      if (action == Qt::MoveAction) {
×
283
        QtPassSettings::getPass()->Move(cleanedSrc, cleanedDestDir);
×
284
      } else if (action == Qt::CopyAction) {
×
285
        QtPassSettings::getPass()->Copy(cleanedSrc, cleanedDestDir);
×
286
      }
287
    }
×
288
  } else if (info.isFile) {
×
289
    // dropped file onto a directory
290
    if (destFileinfo.isDir()) {
×
291
      if (action == Qt::MoveAction) {
×
292
        QtPassSettings::getPass()->Move(cleanedSrc, cleanedDest);
×
293
      } else if (action == Qt::CopyAction) {
×
294
        QtPassSettings::getPass()->Copy(cleanedSrc, cleanedDest);
×
295
      }
296
    } else if (destFileinfo.isFile()) {
×
297
      // dropped file onto a file
298
      int answer = QMessageBox::question(
×
299
          nullptr, tr("force overwrite?"),
×
300
          tr("overwrite %1 with %2?").arg(cleanedDest, cleanedSrc),
×
301
          QMessageBox::Yes | QMessageBox::No);
302
      bool force = answer == QMessageBox::Yes;
×
303
      if (action == Qt::MoveAction) {
×
304
        QtPassSettings::getPass()->Move(cleanedSrc, cleanedDest, force);
×
305
      } else if (action == Qt::CopyAction) {
×
306
        QtPassSettings::getPass()->Copy(cleanedSrc, cleanedDest, force);
×
307
      }
308
    }
309
  }
310
  return true;
311
}
×
312

313
/**
314
 * @brief StoreModel::lessThan
315
 * @param source_left
316
 * @param source_right
317
 * @return
318
 */
319
auto StoreModel::lessThan(const QModelIndex &source_left,
2✔
320
                          const QModelIndex &source_right) const -> bool {
321
/* matches logic in QFileSystemModelSorter::compareNodes() */
322
#ifndef Q_OS_MAC
323
  if (fs && (source_left.column() == 0 || source_left.column() == 1)) {
2✔
324
    bool leftD = fs->isDir(source_left);
2✔
325
    bool rightD = fs->isDir(source_right);
2✔
326

327
    if (leftD ^ rightD) {
2✔
328
      return leftD;
329
    }
330
  }
331
#endif
332

333
  return QSortFilterProxyModel::lessThan(source_left, source_right);
1✔
334
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc