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 "EntityAttributeGrid.h"
21 
22 #include "Model/Object.h"
23 #include "View/EntityAttributeGridTable.h"
24 #include "View/EntityAttributeSelectedCommand.h"
25 #include "View/ViewConstants.h"
26 #include "View/MapDocument.h"
27 #include "View/wxUtils.h"
28 
29 #include <wx/bmpbuttn.h>
30 #include <wx/checkbox.h>
31 #include <wx/sizer.h>
32 
33 namespace TrenchBroom {
34     namespace View {
EntityAttributeGrid(wxWindow * parent,MapDocumentWPtr document)35         EntityAttributeGrid::EntityAttributeGrid(wxWindow* parent, MapDocumentWPtr document) :
36         wxPanel(parent),
37         m_document(document),
38         m_lastHoveredCell(wxGridCellCoords(-1, -1)),
39         m_ignoreSelection(false),
40         m_lastSelectedCol(0) {
41             createGui(document);
42             bindObservers();
43         }
44 
~EntityAttributeGrid()45         EntityAttributeGrid::~EntityAttributeGrid() {
46             unbindObservers();
47         }
48 
OnAttributeGridSize(wxSizeEvent & event)49         void EntityAttributeGrid::OnAttributeGridSize(wxSizeEvent& event) {
50             if (IsBeingDeleted()) return;
51 
52             m_grid->SetColSize(0, 100);
53             const int colSize = std::max(1, m_grid->GetClientSize().x - m_grid->GetColSize(0));
54             m_grid->SetColSize(1, colSize);
55             event.Skip();
56         }
57 
OnAttributeGridSelectCell(wxGridEvent & event)58         void EntityAttributeGrid::OnAttributeGridSelectCell(wxGridEvent& event) {
59             if (IsBeingDeleted()) return;
60             fireSelectionEvent(event.GetRow(), event.GetCol());
61         }
62 
OnAttributeGridTab(wxGridEvent & event)63         void EntityAttributeGrid::OnAttributeGridTab(wxGridEvent& event) {
64             if (IsBeingDeleted()) return;
65 
66             if (event.ShiftDown()) {
67                 if (event.GetCol() > 0)
68                     moveCursorTo(event.GetRow(), event.GetCol() - 1);
69                 else if (event.GetRow() > 0)
70                     moveCursorTo(event.GetRow() - 1, m_grid->GetNumberCols() - 1);
71             } else {
72                 if (event.GetCol() < m_grid->GetNumberCols() - 1)
73                     moveCursorTo(event.GetRow(), event.GetCol() + 1);
74                 else if (event.GetRow() < m_grid->GetNumberRows() - 1)
75                     moveCursorTo(event.GetRow() + 1, 0);
76             }
77         }
78 
moveCursorTo(const int row,const int col)79         void EntityAttributeGrid::moveCursorTo(const int row, const int col) {
80             {
81                 const SetBool ignoreSelection(m_ignoreSelection);
82                 m_grid->GoToCell(row, col);
83                 m_grid->SelectRow(row);
84             }
85             fireSelectionEvent(row, col);
86         }
87 
fireSelectionEvent(const int row,const int col)88         void EntityAttributeGrid::fireSelectionEvent(const int row, const int col) {
89             if (!m_ignoreSelection) {
90                 const Model::AttributeName name = m_table->attributeName(row);
91                 m_lastSelectedName = name;
92                 m_lastSelectedCol = col;
93 
94                 EntityAttributeSelectedCommand command;
95                 command.setName(name);
96                 command.SetEventObject(this);
97                 command.SetId(GetId());
98                 ProcessEvent(command);
99             }
100         }
101 
OnAttributeGridKeyDown(wxKeyEvent & event)102         void EntityAttributeGrid::OnAttributeGridKeyDown(wxKeyEvent& event) {
103             if (IsBeingDeleted()) return;
104 
105             if (isInsertRowShortcut(event)) {
106                 addAttribute();
107             } else if (isRemoveRowShortcut(event)) {
108                 if (canRemoveSelectedAttributes())
109                     removeSelectedAttributes();
110             } else {
111                 event.Skip();
112             }
113         }
114 
OnAttributeGridKeyUp(wxKeyEvent & event)115         void EntityAttributeGrid::OnAttributeGridKeyUp(wxKeyEvent& event) {
116             if (IsBeingDeleted()) return;
117 
118             if (!isInsertRowShortcut(event) && !isRemoveRowShortcut(event))
119                 event.Skip();
120         }
121 
isInsertRowShortcut(const wxKeyEvent & event) const122         bool EntityAttributeGrid::isInsertRowShortcut(const wxKeyEvent& event) const {
123             return event.GetKeyCode() == WXK_RETURN && event.ControlDown();
124         }
125 
isRemoveRowShortcut(const wxKeyEvent & event) const126         bool EntityAttributeGrid::isRemoveRowShortcut(const wxKeyEvent& event) const {
127             return (event.GetKeyCode() == WXK_DELETE || event.GetKeyCode() == WXK_BACK) && !m_grid->IsCellEditControlShown();
128         }
129 
OnAttributeGridMouseMove(wxMouseEvent & event)130         void EntityAttributeGrid::OnAttributeGridMouseMove(wxMouseEvent& event) {
131             if (IsBeingDeleted()) return;
132 
133             int logicalX, logicalY;
134             m_grid->CalcUnscrolledPosition(event.GetX(), event.GetY(), &logicalX, &logicalY);
135 
136             const wxGridCellCoords currentCell = m_grid->XYToCell(logicalX, logicalY);
137             if (m_lastHoveredCell != currentCell) {
138                 const String tooltip = m_table->tooltip(currentCell);
139                 m_grid->SetToolTip(tooltip);
140                 m_lastHoveredCell = currentCell;
141             }
142             event.Skip();
143         }
144 
OnUpdateAttributeView(wxUpdateUIEvent & event)145         void EntityAttributeGrid::OnUpdateAttributeView(wxUpdateUIEvent& event) {
146             if (IsBeingDeleted()) return;
147 
148             MapDocumentSPtr document = lock(m_document);
149             event.Enable(document->hasSelectedNodes());
150         }
151 
OnAddAttributeButton(wxCommandEvent & event)152         void EntityAttributeGrid::OnAddAttributeButton(wxCommandEvent& event) {
153             if (IsBeingDeleted()) return;
154 
155             addAttribute();
156         }
157 
OnRemovePropertiesButton(wxCommandEvent & event)158         void EntityAttributeGrid::OnRemovePropertiesButton(wxCommandEvent& event) {
159             if (IsBeingDeleted()) return;
160 
161             removeSelectedAttributes();
162         }
163 
addAttribute()164         void EntityAttributeGrid::addAttribute() {
165             m_grid->InsertRows(m_table->GetNumberAttributeRows());
166             m_grid->SetFocus();
167             const int row = m_table->GetNumberAttributeRows() - 1;
168             m_grid->SelectRow(row);
169             m_grid->GoToCell(row, 0);
170             m_grid->ShowCellEditControl();
171         }
172 
removeSelectedAttributes()173         void EntityAttributeGrid::removeSelectedAttributes() {
174             assert(canRemoveSelectedAttributes());
175 
176             int firstRowIndex = m_grid->GetNumberRows();
177             wxArrayInt selectedRows = m_grid->GetSelectedRows();
178             wxArrayInt::reverse_iterator it, end;
179             for (it = selectedRows.rbegin(), end = selectedRows.rend(); it != end; ++it) {
180                 m_grid->DeleteRows(*it, 1);
181                 firstRowIndex = std::min(*it, firstRowIndex);
182             }
183 
184             if (firstRowIndex < m_grid->GetNumberRows())
185                 m_grid->SelectRow(firstRowIndex);
186         }
187 
OnShowDefaultPropertiesCheckBox(wxCommandEvent & event)188         void EntityAttributeGrid::OnShowDefaultPropertiesCheckBox(wxCommandEvent& event) {
189             if (IsBeingDeleted()) return;
190 
191             m_table->setShowDefaultRows(event.IsChecked());
192         }
193 
OnUpdateAddAttributeButton(wxUpdateUIEvent & event)194         void EntityAttributeGrid::OnUpdateAddAttributeButton(wxUpdateUIEvent& event) {
195             if (IsBeingDeleted()) return;
196 
197             MapDocumentSPtr document = lock(m_document);
198             event.Enable(document->hasSelectedNodes());
199         }
200 
OnUpdateRemovePropertiesButton(wxUpdateUIEvent & event)201         void EntityAttributeGrid::OnUpdateRemovePropertiesButton(wxUpdateUIEvent& event) {
202             if (IsBeingDeleted()) return;
203 
204             event.Enable(!m_grid->GetSelectedRows().IsEmpty() && canRemoveSelectedAttributes());
205         }
206 
OnUpdateShowDefaultPropertiesCheckBox(wxUpdateUIEvent & event)207         void EntityAttributeGrid::OnUpdateShowDefaultPropertiesCheckBox(wxUpdateUIEvent& event) {
208             if (IsBeingDeleted()) return;
209 
210             event.Check(m_table->showDefaultRows());
211         }
212 
canRemoveSelectedAttributes() const213         bool EntityAttributeGrid::canRemoveSelectedAttributes() const {
214             const wxArrayInt selectedRows = m_grid->GetSelectedRows();
215             wxArrayInt::const_iterator it, end;
216             for (it = selectedRows.begin(), end = selectedRows.end(); it != end; ++it) {
217                 if (!m_table->canRemove(*it))
218                     return false;
219             }
220             return true;
221         }
222 
createGui(MapDocumentWPtr document)223         void EntityAttributeGrid::createGui(MapDocumentWPtr document) {
224             SetBackgroundColour(*wxWHITE);
225 
226             m_table = new EntityAttributeGridTable(document);
227 
228             m_grid = new wxGrid(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
229             m_grid->SetTable(m_table, true, wxGrid::wxGridSelectRows);
230             // m_grid->SetUseNativeColLabels();
231             // m_grid->UseNativeColHeader();
232             m_grid->SetColLabelSize(18);
233             m_grid->SetDefaultCellBackgroundColour(*wxWHITE);
234             m_grid->HideRowLabels();
235 
236             m_grid->DisableColResize(0);
237             m_grid->DisableColResize(1);
238             m_grid->DisableDragColMove();
239             m_grid->DisableDragCell();
240             m_grid->DisableDragColSize();
241             m_grid->DisableDragGridSize();
242             m_grid->DisableDragRowSize();
243 
244             m_grid->Bind(wxEVT_SIZE, &EntityAttributeGrid::OnAttributeGridSize, this);
245             m_grid->Bind(wxEVT_GRID_SELECT_CELL, &EntityAttributeGrid::OnAttributeGridSelectCell, this);
246             m_grid->Bind(wxEVT_GRID_TABBING, &EntityAttributeGrid::OnAttributeGridTab, this);
247             m_grid->Bind(wxEVT_KEY_DOWN, &EntityAttributeGrid::OnAttributeGridKeyDown, this);
248             m_grid->Bind(wxEVT_KEY_UP, &EntityAttributeGrid::OnAttributeGridKeyUp, this);
249             m_grid->GetGridWindow()->Bind(wxEVT_MOTION, &EntityAttributeGrid::OnAttributeGridMouseMove, this);
250             m_grid->Bind(wxEVT_UPDATE_UI, &EntityAttributeGrid::OnUpdateAttributeView, this);
251 
252             wxWindow* addAttributeButton = createBitmapButton(this, "Add.png", "Add a new property");
253             wxWindow* removePropertiesButton = createBitmapButton(this, "Remove.png", "Remove the selected properties");
254 
255             addAttributeButton->Bind(wxEVT_BUTTON, &EntityAttributeGrid::OnAddAttributeButton, this);
256             addAttributeButton->Bind(wxEVT_UPDATE_UI, &EntityAttributeGrid::OnUpdateAddAttributeButton, this);
257             removePropertiesButton->Bind(wxEVT_BUTTON, &EntityAttributeGrid::OnRemovePropertiesButton, this);
258             removePropertiesButton->Bind(wxEVT_UPDATE_UI, &EntityAttributeGrid::OnUpdateRemovePropertiesButton, this);
259 
260             wxCheckBox* showDefaultPropertiesCheckBox = new wxCheckBox(this, wxID_ANY, "Show default properties");
261             showDefaultPropertiesCheckBox->Bind(wxEVT_CHECKBOX, &EntityAttributeGrid::OnShowDefaultPropertiesCheckBox, this);
262             showDefaultPropertiesCheckBox->Bind(wxEVT_UPDATE_UI, &EntityAttributeGrid::OnUpdateShowDefaultPropertiesCheckBox, this);
263 
264             wxSizer* buttonSizer = new wxBoxSizer(wxHORIZONTAL);
265             buttonSizer->Add(addAttributeButton, 0, wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM, LayoutConstants::NarrowVMargin);
266             buttonSizer->Add(removePropertiesButton, 0, wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM, LayoutConstants::NarrowVMargin);
267             buttonSizer->AddSpacer(LayoutConstants::WideHMargin);
268             buttonSizer->Add(showDefaultPropertiesCheckBox, 0, wxALIGN_CENTER_VERTICAL | wxTOP | wxBOTTOM, LayoutConstants::NarrowVMargin);
269             buttonSizer->AddStretchSpacer();
270 
271             wxSizer* sizer = new wxBoxSizer(wxVERTICAL);
272             sizer->Add(m_grid, 1, wxEXPAND);
273             sizer->Add(buttonSizer, 0, wxEXPAND);
274             SetSizer(sizer);
275         }
276 
bindObservers()277         void EntityAttributeGrid::bindObservers() {
278             MapDocumentSPtr document = lock(m_document);
279             document->documentWasNewedNotifier.addObserver(this, &EntityAttributeGrid::documentWasNewed);
280             document->documentWasLoadedNotifier.addObserver(this, &EntityAttributeGrid::documentWasLoaded);
281             document->nodesDidChangeNotifier.addObserver(this, &EntityAttributeGrid::nodesDidChange);
282             document->selectionWillChangeNotifier.addObserver(this, &EntityAttributeGrid::selectionWillChange);
283             document->selectionDidChangeNotifier.addObserver(this, &EntityAttributeGrid::selectionDidChange);
284         }
285 
unbindObservers()286         void EntityAttributeGrid::unbindObservers() {
287             if (!expired(m_document)) {
288                 MapDocumentSPtr document = lock(m_document);
289                 document->documentWasNewedNotifier.removeObserver(this, &EntityAttributeGrid::documentWasNewed);
290                 document->documentWasLoadedNotifier.removeObserver(this, &EntityAttributeGrid::documentWasLoaded);
291                 document->nodesDidChangeNotifier.removeObserver(this, &EntityAttributeGrid::nodesDidChange);
292                 document->selectionWillChangeNotifier.removeObserver(this, &EntityAttributeGrid::selectionWillChange);
293                 document->selectionDidChangeNotifier.removeObserver(this, &EntityAttributeGrid::selectionDidChange);
294             }
295         }
296 
documentWasNewed(MapDocument * document)297         void EntityAttributeGrid::documentWasNewed(MapDocument* document) {
298             updateControls();
299         }
300 
documentWasLoaded(MapDocument * document)301         void EntityAttributeGrid::documentWasLoaded(MapDocument* document) {
302             updateControls();
303         }
304 
nodesDidChange(const Model::NodeList & nodes)305         void EntityAttributeGrid::nodesDidChange(const Model::NodeList& nodes) {
306             updateControls();
307         }
308 
selectionWillChange()309         void EntityAttributeGrid::selectionWillChange() {
310             m_grid->SaveEditControlValue();
311             m_grid->HideCellEditControl();
312         }
313 
selectionDidChange(const Selection & selection)314         void EntityAttributeGrid::selectionDidChange(const Selection& selection) {
315             updateControls();
316         }
317 
updateControls()318         void EntityAttributeGrid::updateControls() {
319             // const SetBool ignoreSelection(m_ignoreSelection);
320             wxGridUpdateLocker lockGrid(m_grid);
321             m_table->update();
322 
323             const int row = m_table->rowForName(m_lastSelectedName);
324             if (row != -1) {
325                 m_grid->SelectRow(row);
326                 m_grid->GoToCell(row, m_lastSelectedCol);
327             } else {
328                 fireSelectionEvent(row, m_lastSelectedCol);
329             }
330         }
331 
selectedRowName() const332         Model::AttributeName EntityAttributeGrid::selectedRowName() const {
333             wxArrayInt selectedRows = m_grid->GetSelectedRows();
334             if (selectedRows.empty())
335                 return "";
336             const int row = selectedRows.front();
337             return m_table->attributeName(row);
338         }
339     }
340 }
341