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