1 /*
2  Copyright (C) 2010-2014 Kristian Duske
3 
4  This file is part of TrenchBroom.
5 
6  TrenchBroom is free software: you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation, either version 3 of the License, or
9  (at your option) any later version.
10 
11  TrenchBroom is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with TrenchBroom. If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include "IssueBrowserView.h"
21 
22 #include "Model/CollectMatchingIssuesVisitor.h"
23 #include "Model/Issue.h"
24 #include "Model/IssueQuickFix.h"
25 #include "Model/World.h"
26 #include "View/MapDocument.h"
27 #include "View/wxUtils.h"
28 
29 #include <wx/menu.h>
30 #include <wx/settings.h>
31 
32 namespace TrenchBroom {
33     namespace View {
IssueBrowserView(wxWindow * parent,MapDocumentWPtr document)34         IssueBrowserView::IssueBrowserView(wxWindow* parent, MapDocumentWPtr document) :
35         wxListCtrl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxLC_VIRTUAL | wxLC_HRULES | wxLC_VRULES | wxBORDER_NONE),
36         m_document(document),
37         m_hiddenGenerators(0),
38         m_showHiddenIssues(false) {
39             AppendColumn("Line");
40             AppendColumn("Description");
41 
42             reset();
43             bindEvents();
44         }
45 
hiddenGenerators() const46         int IssueBrowserView::hiddenGenerators() const {
47             return m_hiddenGenerators;
48         }
49 
setHiddenGenerators(const int hiddenGenerators)50         void IssueBrowserView::setHiddenGenerators(const int hiddenGenerators) {
51             if (hiddenGenerators == m_hiddenGenerators)
52                 return;
53             m_hiddenGenerators = hiddenGenerators;
54             reset();
55         }
56 
setShowHiddenIssues(const bool show)57         void IssueBrowserView::setShowHiddenIssues(const bool show) {
58             m_showHiddenIssues = show;
59             reset();
60         }
61 
reset()62         void IssueBrowserView::reset() {
63             updateIssues();
64             SetItemCount(static_cast<long>(m_issues.size()));
65             Refresh();
66         }
67 
OnSize(wxSizeEvent & event)68         void IssueBrowserView::OnSize(wxSizeEvent& event) {
69             if (IsBeingDeleted()) return;
70 
71             const int newWidth = std::max(1, GetClientSize().x - GetColumnWidth(0));
72             SetColumnWidth(1, newWidth);
73             event.Skip();
74         }
75 
OnItemRightClick(wxListEvent & event)76         void IssueBrowserView::OnItemRightClick(wxListEvent& event) {
77             if (IsBeingDeleted()) return;
78 
79             if (GetSelectedItemCount() == 0 || event.GetIndex() < 0)
80                 return;
81 
82             wxMenu popupMenu;
83             popupMenu.Append(ShowIssuesCommandId, "Show");
84             popupMenu.Append(HideIssuesCommandId, "Hide");
85             popupMenu.Bind(wxEVT_MENU, &IssueBrowserView::OnShowIssues, this, ShowIssuesCommandId);
86             popupMenu.Bind(wxEVT_MENU, &IssueBrowserView::OnHideIssues, this, HideIssuesCommandId);
87 
88             const Model::IssueQuickFixList quickFixes = collectQuickFixes(getSelection());
89             if (!quickFixes.empty()) {
90                 wxMenu* quickFixMenu = new wxMenu();
91 
92                 for (size_t i = 0; i < quickFixes.size(); ++i) {
93                     Model::IssueQuickFix* quickFix = quickFixes[i];
94                     const int quickFixId = FixObjectsBaseId + static_cast<int>(i);
95                     quickFixMenu->Append(quickFixId, quickFix->description());
96 
97                     wxVariant* data = new wxVariant(reinterpret_cast<void*>(quickFix));
98                     quickFixMenu->Bind(wxEVT_MENU, &IssueBrowserView::OnApplyQuickFix, this, quickFixId, quickFixId, data);
99                 }
100 
101                 popupMenu.AppendSeparator();
102                 popupMenu.AppendSubMenu(quickFixMenu, "Fix");
103             }
104 
105             PopupMenu(&popupMenu);
106         }
107 
OnItemSelectionChanged(wxListEvent & event)108         void IssueBrowserView::OnItemSelectionChanged(wxListEvent& event) {
109             if (IsBeingDeleted()) return;
110 
111             updateSelection();
112         }
113 
OnShowIssues(wxCommandEvent & event)114         void IssueBrowserView::OnShowIssues(wxCommandEvent& event) {
115             if (IsBeingDeleted()) return;
116 
117             setIssueVisibility(true);
118         }
119 
OnHideIssues(wxCommandEvent & event)120         void IssueBrowserView::OnHideIssues(wxCommandEvent& event) {
121             if (IsBeingDeleted()) return;
122 
123             setIssueVisibility(false);
124         }
125 
126         class IssueBrowserView::IssueVisible {
127             int m_hiddenTypes;
128             bool m_showHiddenIssues;
129         public:
IssueVisible(const int hiddenTypes,const bool showHiddenIssues)130             IssueVisible(const int hiddenTypes, const bool showHiddenIssues) :
131             m_hiddenTypes(hiddenTypes),
132             m_showHiddenIssues(showHiddenIssues) {}
133 
operator ()(const Model::Issue * issue) const134             bool operator()(const Model::Issue* issue) const {
135                 return m_showHiddenIssues || (!issue->hidden() && (issue->type() & m_hiddenTypes) == 0);
136             }
137         };
138 
139         class IssueBrowserView::IssueCmp {
140         public:
operator ()(const Model::Issue * lhs,const Model::Issue * rhs) const141             bool operator()(const Model::Issue* lhs, const Model::Issue* rhs) const {
142                 return lhs->seqId() > rhs->seqId();
143             }
144         };
145 
updateSelection()146         void IssueBrowserView::updateSelection() {
147             const IndexList selection = getSelection();
148 
149             Model::NodeList nodes;
150             for (size_t i = 0; i < selection.size(); ++i) {
151                 Model::Issue* issue = m_issues[selection[i]];
152                 issue->addSelectableNodes(nodes);
153             }
154 
155             MapDocumentSPtr document = lock(m_document);
156             document->deselectAll();
157             document->select(nodes);
158         }
159 
updateIssues()160         void IssueBrowserView::updateIssues() {
161             m_issues.clear();
162 
163             MapDocumentSPtr document = lock(m_document);
164             Model::World* world = document->world();
165             if (world != NULL) {
166                 const Model::IssueGeneratorList& issueGenerators = world->registeredIssueGenerators();
167                 Model::CollectMatchingIssuesVisitor<IssueVisible> visitor(issueGenerators, IssueVisible(m_hiddenGenerators, m_showHiddenIssues));
168                 world->acceptAndRecurse(visitor);
169                 m_issues = visitor.issues();
170                 VectorUtils::sort(m_issues, IssueCmp());
171             }
172         }
173 
OnApplyQuickFix(wxCommandEvent & event)174         void IssueBrowserView::OnApplyQuickFix(wxCommandEvent& event) {
175             if (IsBeingDeleted()) return;
176 
177             const wxVariant* data = static_cast<wxVariant*>(event.GetEventUserData());
178             assert(data != NULL);
179 
180             const Model::IssueQuickFix* quickFix = reinterpret_cast<const Model::IssueQuickFix*>(data->GetVoidPtr());
181             assert(quickFix != NULL);
182 
183             MapDocumentSPtr document = lock(m_document);
184             const Model::IssueList issues = collectIssues(getSelection());
185 
186             const Transaction transaction(document, "Apply Quick Fix (" + quickFix->description() + ")");
187             updateSelection();
188             quickFix->apply(document.get(), issues);
189         }
190 
collectIssues(const IndexList & indices) const191         Model::IssueList IssueBrowserView::collectIssues(const IndexList& indices) const {
192             Model::IssueList result;
193             for (size_t i = 0; i < indices.size(); ++i)
194                 result.push_back(m_issues[indices[i]]);
195             return result;
196         }
197 
collectQuickFixes(const IndexList & indices) const198         Model::IssueQuickFixList IssueBrowserView::collectQuickFixes(const IndexList& indices) const {
199             if (indices.empty())
200                 return Model::IssueQuickFixList(0);
201 
202             Model::IssueType issueTypes = ~0;
203             for (size_t i = 0; i < indices.size(); ++i) {
204                 const Model::Issue* issue = m_issues[indices[i]];
205                 issueTypes &= issue->type();
206             }
207 
208             MapDocumentSPtr document = lock(m_document);
209             const Model::World* world = document->world();
210             return world->quickFixes(issueTypes);
211         }
212 
issueTypeMask() const213         Model::IssueType IssueBrowserView::issueTypeMask() const {
214             Model::IssueType result = ~static_cast<Model::IssueType>(0);
215             const IndexList selection = getSelection();
216             for (size_t i = 0; i < selection.size(); ++i) {
217                 Model::Issue* issue = m_issues[selection[i]];
218                 result &= issue->type();
219             }
220             return result;
221         }
222 
setIssueVisibility(const bool show)223         void IssueBrowserView::setIssueVisibility(const bool show) {
224             const IndexList selection = getSelection();
225 
226             MapDocumentSPtr document = lock(m_document);
227             for (size_t i = 0; i < selection.size(); ++i) {
228                 Model::Issue* issue = m_issues[selection[i]];
229                 document->setIssueHidden(issue, !show);
230             }
231 
232             reset();
233         }
234 
getSelection() const235         IssueBrowserView::IndexList IssueBrowserView::getSelection() const {
236             return getListCtrlSelection(this);
237         }
238 
OnGetItemAttr(const long item) const239         wxListItemAttr* IssueBrowserView::OnGetItemAttr(const long item) const {
240             assert(item >= 0 && static_cast<size_t>(item) < m_issues.size());
241 
242             static wxListItemAttr attr;
243 
244             Model::Issue* issue = m_issues[static_cast<size_t>(item)];
245             if (issue->hidden()) {
246                 attr.SetFont(GetFont().Italic());
247                 return &attr;
248             }
249 
250             return NULL;
251         }
252 
OnGetItemText(const long item,const long column) const253         wxString IssueBrowserView::OnGetItemText(const long item, const long column) const {
254             assert(item >= 0 && static_cast<size_t>(item) < m_issues.size());
255             assert(column >= 0 && column < 2);
256 
257             Model::Issue* issue = m_issues[static_cast<size_t>(item)];
258             if (column == 0) {
259                 wxString result;
260                 result << issue->lineNumber();
261                 return result;
262             } else {
263                 return issue->description();
264             }
265         }
266 
bindEvents()267         void IssueBrowserView::bindEvents() {
268             Bind(wxEVT_SIZE, &IssueBrowserView::OnSize, this);
269             Bind(wxEVT_LIST_ITEM_RIGHT_CLICK, &IssueBrowserView::OnItemRightClick, this);
270             Bind(wxEVT_LIST_ITEM_SELECTED, &IssueBrowserView::OnItemSelectionChanged, this);
271             Bind(wxEVT_LIST_ITEM_DESELECTED, &IssueBrowserView::OnItemSelectionChanged, this);
272         }
273     }
274 }
275