1 #include "contentselector.hpp"
2
3 #include <components/contentselector/model/esmfile.hpp>
4
5 #include <QSortFilterProxyModel>
6
7 #include <QMenu>
8 #include <QContextMenuEvent>
9
10 #include <QClipboard>
11 #include <QModelIndex>
12
ContentSelector(QWidget * parent)13 ContentSelectorView::ContentSelector::ContentSelector(QWidget *parent) :
14 QObject(parent)
15 {
16 ui.setupUi(parent);
17 ui.addonView->setDragDropMode(QAbstractItemView::InternalMove);
18
19 buildContentModel();
20 buildGameFileView();
21 buildAddonView();
22 }
23
buildContentModel()24 void ContentSelectorView::ContentSelector::buildContentModel()
25 {
26 QIcon warningIcon(ui.addonView->style()->standardIcon(QStyle::SP_MessageBoxWarning).pixmap(QSize(16, 15)));
27 mContentModel = new ContentSelectorModel::ContentModel(this, warningIcon);
28 }
29
buildGameFileView()30 void ContentSelectorView::ContentSelector::buildGameFileView()
31 {
32 ui.gameFileView->setVisible (true);
33
34 ui.gameFileView->setPlaceholderText(QString("Select a game file..."));
35
36 connect (ui.gameFileView, SIGNAL (currentIndexChanged(int)),
37 this, SLOT (slotCurrentGameFileIndexChanged(int)));
38
39 ui.gameFileView->setCurrentIndex(-1);
40 ui.gameFileView->setCurrentIndex(0);
41 }
42
43 class AddOnProxyModel : public QSortFilterProxyModel
44 {
45 public:
AddOnProxyModel(QObject * parent=nullptr)46 explicit AddOnProxyModel(QObject* parent = nullptr) :
47 QSortFilterProxyModel(parent)
48 {}
49
filterAcceptsRow(int sourceRow,const QModelIndex & sourceParent) const50 bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override
51 {
52 static const QString ContentTypeAddon = QString::number((int)ContentSelectorModel::ContentType_Addon);
53
54 QModelIndex nameIndex = sourceModel()->index(sourceRow, 0, sourceParent);
55 const QString userRole = sourceModel()->data(nameIndex, Qt::UserRole).toString();
56
57 return QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent) && userRole == ContentTypeAddon;
58 }
59 };
60
buildAddonView()61 void ContentSelectorView::ContentSelector::buildAddonView()
62 {
63 ui.addonView->setVisible (true);
64
65 mAddonProxyModel = new AddOnProxyModel(this);
66 mAddonProxyModel->setFilterRegExp(searchFilter()->text());
67 mAddonProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
68 mAddonProxyModel->setDynamicSortFilter (true);
69 mAddonProxyModel->setSourceModel (mContentModel);
70
71 connect(ui.searchFilter, SIGNAL(textEdited(QString)), mAddonProxyModel, SLOT(setFilterWildcard(QString)));
72 connect(ui.searchFilter, SIGNAL(textEdited(QString)), this, SLOT(slotSearchFilterTextChanged(QString)));
73
74 ui.addonView->setModel(mAddonProxyModel);
75
76 connect(ui.addonView, SIGNAL(activated(const QModelIndex&)), this, SLOT(slotAddonTableItemActivated(const QModelIndex&)));
77 connect(mContentModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), this, SIGNAL(signalAddonDataChanged(QModelIndex,QModelIndex)));
78 buildContextMenu();
79 }
80
buildContextMenu()81 void ContentSelectorView::ContentSelector::buildContextMenu()
82 {
83 ui.addonView->setContextMenuPolicy(Qt::CustomContextMenu);
84 connect(ui.addonView, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(slotShowContextMenu(const QPoint&)));
85
86 mContextMenu = new QMenu(ui.addonView);
87 mContextMenu->addAction(tr("&Check Selected"), this, SLOT(slotCheckMultiSelectedItems()));
88 mContextMenu->addAction(tr("&Uncheck Selected"), this, SLOT(slotUncheckMultiSelectedItems()));
89 mContextMenu->addAction(tr("&Copy Path(s) to Clipboard"), this, SLOT(slotCopySelectedItemsPaths()));
90 }
91
setProfileContent(const QStringList & fileList)92 void ContentSelectorView::ContentSelector::setProfileContent(const QStringList &fileList)
93 {
94 clearCheckStates();
95
96 for (const QString &filepath : fileList)
97 {
98 const ContentSelectorModel::EsmFile *file = mContentModel->item(filepath);
99 if (file && file->isGameFile())
100 {
101 setGameFile (filepath);
102 break;
103 }
104 }
105
106 setContentList(fileList);
107 }
108
setGameFile(const QString & filename)109 void ContentSelectorView::ContentSelector::setGameFile(const QString &filename)
110 {
111 int index = -1;
112
113 if (!filename.isEmpty())
114 {
115 const ContentSelectorModel::EsmFile *file = mContentModel->item (filename);
116 index = ui.gameFileView->findText (file->fileName());
117
118 //verify that the current index is also checked in the model
119 if (!mContentModel->setCheckState(filename, true))
120 {
121 //throw error in case file not found?
122 return;
123 }
124 }
125
126 ui.gameFileView->setCurrentIndex(index);
127 }
128
clearCheckStates()129 void ContentSelectorView::ContentSelector::clearCheckStates()
130 {
131 mContentModel->uncheckAll();
132 }
133
setEncoding(const QString & encoding)134 void ContentSelectorView::ContentSelector::setEncoding(const QString &encoding)
135 {
136 mContentModel->setEncoding(encoding);
137 }
138
setContentList(const QStringList & list)139 void ContentSelectorView::ContentSelector::setContentList(const QStringList &list)
140 {
141 if (list.isEmpty())
142 {
143 slotCurrentGameFileIndexChanged (ui.gameFileView->currentIndex());
144 }
145 else
146 mContentModel->setContentList(list);
147 }
148
149 ContentSelectorModel::ContentFileList
selectedFiles() const150 ContentSelectorView::ContentSelector::selectedFiles() const
151 {
152 if (!mContentModel)
153 return ContentSelectorModel::ContentFileList();
154
155 return mContentModel->checkedItems();
156 }
157
addFiles(const QString & path)158 void ContentSelectorView::ContentSelector::addFiles(const QString &path)
159 {
160 mContentModel->addFiles(path);
161
162 // add any game files to the combo box
163 for (const QString& gameFileName : mContentModel->gameFiles())
164 {
165 if (ui.gameFileView->findText(gameFileName) == -1)
166 {
167 ui.gameFileView->addItem(gameFileName);
168 }
169 }
170
171 if (ui.gameFileView->currentIndex() != -1)
172 ui.gameFileView->setCurrentIndex(-1);
173
174 mContentModel->uncheckAll();
175 }
176
clearFiles()177 void ContentSelectorView::ContentSelector::clearFiles()
178 {
179 mContentModel->clearFiles();
180 }
181
currentFile() const182 QString ContentSelectorView::ContentSelector::currentFile() const
183 {
184 QModelIndex currentIdx = ui.addonView->currentIndex();
185
186 if (!currentIdx.isValid())
187 return ui.gameFileView->currentText();
188
189 QModelIndex idx = mContentModel->index(mAddonProxyModel->mapToSource(currentIdx).row(), 0, QModelIndex());
190 return mContentModel->data(idx, Qt::DisplayRole).toString();
191 }
192
slotCurrentGameFileIndexChanged(int index)193 void ContentSelectorView::ContentSelector::slotCurrentGameFileIndexChanged(int index)
194 {
195 static int oldIndex = -1;
196
197 if (index != oldIndex)
198 {
199 if (oldIndex > -1)
200 {
201 setGameFileSelected(oldIndex, false);
202 }
203
204 oldIndex = index;
205
206 setGameFileSelected(index, true);
207 mContentModel->checkForLoadOrderErrors();
208 }
209
210 emit signalCurrentGamefileIndexChanged (index);
211 }
212
setGameFileSelected(int index,bool selected)213 void ContentSelectorView::ContentSelector::setGameFileSelected(int index, bool selected)
214 {
215 QString fileName = ui.gameFileView->itemText(index);
216 const ContentSelectorModel::EsmFile* file = mContentModel->item(fileName);
217 if (file != nullptr)
218 {
219 QModelIndex index2(mContentModel->indexFromItem(file));
220 mContentModel->setData(index2, selected, Qt::UserRole + 1);
221 }
222 }
223
slotAddonTableItemActivated(const QModelIndex & index)224 void ContentSelectorView::ContentSelector::slotAddonTableItemActivated(const QModelIndex &index)
225 {
226 // toggles check state when an AddOn file is double clicked or activated by keyboard
227 QModelIndex sourceIndex = mAddonProxyModel->mapToSource (index);
228
229 if (!mContentModel->isEnabled (sourceIndex))
230 return;
231
232 Qt::CheckState checkState = Qt::Unchecked;
233
234 if (mContentModel->data(sourceIndex, Qt::CheckStateRole).toInt() == Qt::Unchecked)
235 checkState = Qt::Checked;
236
237 mContentModel->setData(sourceIndex, checkState, Qt::CheckStateRole);
238 }
239
slotShowContextMenu(const QPoint & pos)240 void ContentSelectorView::ContentSelector::slotShowContextMenu(const QPoint& pos)
241 {
242 QPoint globalPos = ui.addonView->viewport()->mapToGlobal(pos);
243 mContextMenu->exec(globalPos);
244 }
245
setCheckStateForMultiSelectedItems(bool checked)246 void ContentSelectorView::ContentSelector::setCheckStateForMultiSelectedItems(bool checked)
247 {
248 Qt::CheckState checkState = checked ? Qt::Checked : Qt::Unchecked;
249 for (const QModelIndex& index : ui.addonView->selectionModel()->selectedIndexes())
250 {
251 QModelIndex sourceIndex = mAddonProxyModel->mapToSource(index);
252 if (mContentModel->data(sourceIndex, Qt::CheckStateRole).toInt() != checkState)
253 {
254 mContentModel->setData(sourceIndex, checkState, Qt::CheckStateRole);
255 }
256 }
257 }
258
slotUncheckMultiSelectedItems()259 void ContentSelectorView::ContentSelector::slotUncheckMultiSelectedItems()
260 {
261 setCheckStateForMultiSelectedItems(false);
262 }
263
slotCheckMultiSelectedItems()264 void ContentSelectorView::ContentSelector::slotCheckMultiSelectedItems()
265 {
266 setCheckStateForMultiSelectedItems(true);
267 }
268
slotCopySelectedItemsPaths()269 void ContentSelectorView::ContentSelector::slotCopySelectedItemsPaths()
270 {
271 QClipboard *clipboard = QApplication::clipboard();
272 QString filepaths;
273 for (const QModelIndex& index : ui.addonView->selectionModel()->selectedIndexes())
274 {
275 int row = mAddonProxyModel->mapToSource(index).row();
276 const ContentSelectorModel::EsmFile *file = mContentModel->item(row);
277 filepaths += file->filePath() + "\n";
278 }
279
280 if (!filepaths.isEmpty())
281 {
282 clipboard->setText(filepaths);
283 }
284 }
285
slotSearchFilterTextChanged(const QString & newText)286 void ContentSelectorView::ContentSelector::slotSearchFilterTextChanged(const QString& newText)
287 {
288 ui.addonView->setDragEnabled(newText.isEmpty());
289 }
290