1 #include "filezilla.h"
2 #include "customheightlistctrl.h"
3 
4 #include <algorithm>
5 
BEGIN_EVENT_TABLE(wxCustomHeightListCtrl,wxScrolledWindow)6 BEGIN_EVENT_TABLE(wxCustomHeightListCtrl, wxScrolledWindow)
7 EVT_MOUSE_EVENTS(wxCustomHeightListCtrl::OnMouseEvent)
8 EVT_SIZE(wxCustomHeightListCtrl::OnSize)
9 END_EVENT_TABLE()
10 
11 wxCustomHeightListCtrl::wxCustomHeightListCtrl(wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxString& name)
12 	: wxScrolledWindow(parent, id, pos, size, style, name)
13 {
14 }
15 
SetLineHeight(int height)16 void wxCustomHeightListCtrl::SetLineHeight(int height)
17 {
18 	m_lineHeight = height;
19 
20 	int posx, posy;
21 	GetViewStart(&posx, &posy);
22 	SetScrollbars(0, m_lineHeight, 0, m_rows.size(), 0, posy);
23 
24 	Refresh();
25 }
26 
AdjustView()27 void wxCustomHeightListCtrl::AdjustView()
28 {
29 	int posx, posy;
30 	GetViewStart(&posx, &posy);
31 
32 #ifdef __WXGTK__
33 	// When decreasing scrollbar range, wxGTK does not seem to adjust child position
34 	// if viewport gets moved
35 	wxPoint old_view;
36 	GetViewStart(&old_view.x, &old_view.y);
37 #endif
38 
39 	SetScrollbars(0, m_lineHeight, 0, m_rows.size(), 0, posy);
40 
41 #ifdef __WXGTK__
42 	wxPoint new_view;
43 	GetViewStart(&new_view.x, &new_view.y);
44 	int delta_y = m_lineHeight *(old_view.y - new_view.y);
45 
46 	if (delta_y) {
47 		wxWindowList::compatibility_iterator iter = GetChildren().GetFirst();
48 		while (iter) {
49 			wxWindow* child = iter->GetData();
50 			wxPoint pos = child->GetPosition();
51 			pos.y -= delta_y;
52 			child->SetPosition(pos);
53 
54 			iter = iter->GetNext();
55 		}
56 	}
57 #endif
58 }
59 
SetFocus()60 void wxCustomHeightListCtrl::SetFocus()
61 {
62 	wxWindow::SetFocus();
63 }
64 
OnDraw(wxDC & dc)65 void wxCustomHeightListCtrl::OnDraw(wxDC& dc)
66 {
67 	wxSize size = GetClientSize();
68 
69 	dc.SetBrush(wxBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT)));
70 	dc.SetPen(*wxTRANSPARENT_PEN);
71 
72 	for (auto const& selected : m_selectedLines) {
73 		if (selected == m_focusedLine) {
74 			dc.SetPen(wxPen(wxColour(0, 0, 0), 1, wxPENSTYLE_DOT));
75 		}
76 		else {
77 			dc.SetPen(*wxTRANSPARENT_PEN);
78 		}
79 		dc.DrawRectangle(0, m_lineHeight * selected, size.GetWidth(), m_lineHeight);
80 	}
81 	if (m_focusedLine != npos && m_selectedLines.find(m_focusedLine) == m_selectedLines.end()) {
82 		dc.SetBrush(wxBrush(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)));
83 		dc.SetPen(wxPen(wxColour(0, 0, 0), 1, wxPENSTYLE_DOT));
84 		dc.DrawRectangle(0, m_lineHeight * m_focusedLine, size.GetWidth(), m_lineHeight);
85 	}
86 }
87 
OnMouseEvent(wxMouseEvent & event)88 void wxCustomHeightListCtrl::OnMouseEvent(wxMouseEvent& event)
89 {
90 	bool changed = false;
91 	if (event.ButtonDown() && m_allow_selection) {
92 		wxPoint pos = event.GetPosition();
93 		int x, y;
94 		CalcUnscrolledPosition(pos.x, pos.y, &x, &y);
95 		if (y < 0 || y > static_cast<int>(m_lineHeight * m_rows.size())) {
96 			m_focusedLine = npos;
97 			m_selectedLines.clear();
98 			changed = true;
99 		}
100 		else {
101 			size_t line = static_cast<size_t>(y / m_lineHeight);
102 
103 			if (event.ShiftDown()) {
104 				if (m_focusedLine == npos) {
105 					changed |= m_selectedLines.insert(line).second;
106 				}
107 				else if (line < m_focusedLine) {
108 					for (size_t i = line; i <= m_focusedLine; ++i) {
109 						changed |= m_selectedLines.insert(i).second;
110 					}
111 				}
112 				else {
113 					for (size_t i = line; i >= m_focusedLine && i != npos; --i) {
114 						changed |= m_selectedLines.insert(i).second;
115 					}
116 				}
117 			}
118 			else if (event.ControlDown()) {
119 				if (m_selectedLines.find(line) == m_selectedLines.end()) {
120 					m_selectedLines.insert(line);
121 				}
122 				else {
123 					m_selectedLines.erase(line);
124 				}
125 				changed = true;
126 			}
127 			else {
128 				m_selectedLines.clear();
129 				m_selectedLines.insert(line);
130 				changed = true;
131 			}
132 
133 			m_focusedLine = line;
134 		}
135 		Refresh();
136 	}
137 
138 	event.Skip();
139 
140 	if (changed) {
141 		wxCommandEvent evt(wxEVT_COMMAND_LISTBOX_SELECTED, GetId());
142 		ProcessEvent(evt);
143 	}
144 }
145 
ClearSelection()146 void wxCustomHeightListCtrl::ClearSelection()
147 {
148 	m_selectedLines.clear();
149 	m_focusedLine = npos;
150 
151 	AdjustView();
152 	Refresh();
153 }
154 
GetSelection() const155 std::set<size_t> wxCustomHeightListCtrl::GetSelection() const
156 {
157 	return m_selectedLines;
158 }
159 
GetRowCount() const160 size_t wxCustomHeightListCtrl::GetRowCount() const
161 {
162 	return m_rows.size();
163 }
164 
SelectLine(size_t line)165 void wxCustomHeightListCtrl::SelectLine(size_t line)
166 {
167 	if (!m_allow_selection) {
168 		return;
169 	}
170 
171 	m_selectedLines.clear();
172 	m_focusedLine = line;
173 	if (line != npos) {
174 		m_selectedLines.insert(line);
175 	}
176 
177 	Refresh();
178 }
179 
AllowSelection(bool allow_selection)180 void wxCustomHeightListCtrl::AllowSelection(bool allow_selection)
181 {
182 	m_allow_selection = allow_selection;
183 	if (!allow_selection) {
184 		ClearSelection();
185 	}
186 }
187 
InsertRow(wxSizer * sizer,size_t pos)188 void wxCustomHeightListCtrl::InsertRow(wxSizer* sizer, size_t pos)
189 {
190 	assert(sizer);
191 	assert(pos <= m_rows.size());
192 	m_rows.insert(m_rows.begin() + pos, sizer);
193 
194 	sizer->SetContainingWindow(this);
195 
196 	AdjustView();
197 
198 	int left = 0;
199 	int top = 0;
200 	CalcScrolledPosition(0, 0, &left, &top);
201 
202 	int const width = GetClientSize().GetWidth();
203 	for (size_t i = pos; i < m_rows.size(); ++i) {
204 		m_rows[i]->SetDimension(left, m_lineHeight * i + top, width, m_lineHeight);
205 	}
206 
207 	Refresh();
208 
209 #ifdef __WXGTK__
210 	// Needed for Ubuntu's shitty overlay scrollbar, it changes the layout asynchronously...
211 	CallAfter([this](){
212 		wxSizeEvent ev;
213 		OnSize(ev);
214 	});
215 #endif
216 }
217 
DeleteRow(wxSizer * row)218 void wxCustomHeightListCtrl::DeleteRow(wxSizer *row)
219 {
220 	auto it = std::find(m_rows.begin(), m_rows.end(), row);
221 	if (it != m_rows.end()) {
222 		DeleteRow(it - m_rows.begin());
223 	}
224 }
225 
DeleteRow(size_t pos)226 void wxCustomHeightListCtrl::DeleteRow(size_t pos)
227 {
228 	assert(pos < m_rows.size());
229 	m_rows[pos]->SetContainingWindow(0);
230 	m_rows.erase(m_rows.begin() + pos);
231 
232 	std::set<size_t> selectedLines;
233 	m_selectedLines.swap(selectedLines);
234 	for (auto const& selected : selectedLines) {
235 		if (selected < m_rows.size()) {
236 			m_selectedLines.insert(selected);
237 		}
238 	}
239 
240 	AdjustView();
241 
242 	if (m_focusedLine >= m_rows.size()) {
243 		m_focusedLine = npos;
244 	}
245 
246 	int left = 0;
247 	int top = 0;
248 	CalcScrolledPosition(0, 0, &left, &top);
249 
250 	int const width = GetClientSize().GetWidth();
251 
252 	// Intentionally update all: if y position changes, by the time OnSize is called in
253 	// response to AdjustView, internal state isn't quite correct
254 	for (size_t i = 0; i < m_rows.size(); ++i) {
255 		m_rows[i]->SetDimension(left, m_lineHeight * i + top, width, m_lineHeight);
256 	}
257 
258 	Refresh();
259 }
260 
ClearRows()261 void wxCustomHeightListCtrl::ClearRows()
262 {
263 	m_rows.clear();
264 }
265 
OnSize(wxSizeEvent & event)266 void wxCustomHeightListCtrl::OnSize(wxSizeEvent& event)
267 {
268 	event.Skip();
269 
270 	int const width = GetClientSize().GetWidth();
271 
272 	int left = 0;
273 	int top = 0;
274 	CalcScrolledPosition(0, 0, &left, &top);
275 	for (size_t i = 0; i < m_rows.size(); ++i) {
276 		m_rows[i]->SetDimension(left, m_lineHeight * i + top, width, m_lineHeight);
277 	}
278 }
279