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