Allow handling EVT_UPDATE_UI for wxID_UNDO/REDO at wxDocument level.
[wxWidgets.git] / src / common / windowid.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/common/windowid.cpp
3 // Purpose: wxWindowID class - a class for managing window ids
4 // Author: Brian Vanderburg II
5 // Created: 2007-09-21
6 // RCS-ID: $Id$
7 // Copyright: (c) 2007 Brian Vanderburg II
8 // Licence: wxWindows licence
9 ///////////////////////////////////////////////////////////////////////////////
10
11 // ----------------------------------------------------------------------------
12 // Needed headers
13 // ----------------------------------------------------------------------------
14 #include "wx/wxprec.h"
15
16 #ifdef __BORLANDC__
17 #pragma hdrstop
18 #endif
19
20 #ifndef WX_PRECOMP
21 #include "wx/log.h"
22 #include "wx/intl.h"
23 #endif //WX_PRECOMP
24
25 #include "wx/hashmap.h"
26
27 // Not needed, included in defs.h
28 // #include "wx/windowid.h"
29
30 #define wxTRACE_WINDOWID wxT("windowid")
31
32 namespace
33 {
34
35 #if wxUSE_AUTOID_MANAGEMENT
36
37
38 // initially no ids are in use and we allocate them consecutively, but after we
39 // exhaust the entire range, we wrap around and reuse the ids freed in the
40 // meanwhile
41 static const wxUint8 ID_FREE = 0;
42 static const wxUint8 ID_STARTCOUNT = 1;
43 static const wxUint8 ID_COUNTTOOLARGE = 254;
44 static const wxUint8 ID_RESERVED = 255;
45
46 // we use a two level count, most IDs will be used less than ID_COUNTTOOLARGE-1
47 // thus we store their count directly in this array, however when the same ID
48 // is reused a great number of times (more than or equal to ID_COUNTTOOLARGE),
49 // the hash map stores the actual count
50 wxUint8 gs_autoIdsRefCount[wxID_AUTO_HIGHEST - wxID_AUTO_LOWEST + 1] = { 0 };
51
52 // NB: this variable is allocated (again) only when an ID gets at least
53 // ID_COUNTTOOLARGE refs, and is freed when the latest entry in the map gets
54 // freed. The cell storing the count for an ID is freed only when its count
55 // gets to zero (not when it goes below ID_COUNTTOOLARGE, so as to avoid
56 // degenerate cases)
57 wxLongToLongHashMap *gs_autoIdsLargeRefCount = NULL;
58
59 // this is an optimization used until we wrap around wxID_AUTO_HIGHEST: if this
60 // value is < wxID_AUTO_HIGHEST we know that we haven't wrapped yet and so can
61 // allocate the ids simply by incrementing it
62 wxWindowID gs_nextAutoId = wxID_AUTO_LOWEST;
63
64 // Reserve an ID
65 void ReserveIdRefCount(wxWindowID winid)
66 {
67 wxCHECK_RET(winid >= wxID_AUTO_LOWEST && winid <= wxID_AUTO_HIGHEST,
68 wxT("invalid id range"));
69
70 winid -= wxID_AUTO_LOWEST;
71
72 wxCHECK_RET(gs_autoIdsRefCount[winid] == ID_FREE,
73 wxT("id already in use or already reserved"));
74 gs_autoIdsRefCount[winid] = ID_RESERVED;
75 }
76
77 // Unreserve and id
78 void UnreserveIdRefCount(wxWindowID winid)
79 {
80 wxCHECK_RET(winid >= wxID_AUTO_LOWEST && winid <= wxID_AUTO_HIGHEST,
81 wxT("invalid id range"));
82
83 winid -= wxID_AUTO_LOWEST;
84
85 wxCHECK_RET(gs_autoIdsRefCount[winid] == ID_RESERVED,
86 wxT("id already in use or not reserved"));
87 gs_autoIdsRefCount[winid] = ID_FREE;
88 }
89
90 // Get the usage count of an id
91 int GetIdRefCount(wxWindowID winid)
92 {
93 wxCHECK_MSG(winid >= wxID_AUTO_LOWEST && winid <= wxID_AUTO_HIGHEST, 0,
94 wxT("invalid id range"));
95
96 winid -= wxID_AUTO_LOWEST;
97 int refCount = gs_autoIdsRefCount[winid];
98 if (refCount == ID_COUNTTOOLARGE)
99 refCount = (*gs_autoIdsLargeRefCount)[winid];
100 return refCount;
101 }
102
103 // Increase the count for an id
104 void IncIdRefCount(wxWindowID winid)
105 {
106 wxCHECK_RET(winid >= wxID_AUTO_LOWEST && winid <= wxID_AUTO_HIGHEST,
107 wxT("invalid id range"));
108
109 winid -= wxID_AUTO_LOWEST;
110
111 wxCHECK_RET(gs_autoIdsRefCount[winid] != ID_FREE, wxT("id should first be reserved"));
112
113 if(gs_autoIdsRefCount[winid] == ID_RESERVED)
114 {
115 gs_autoIdsRefCount[winid] = ID_STARTCOUNT;
116 }
117 else if (gs_autoIdsRefCount[winid] >= ID_COUNTTOOLARGE-1)
118 {
119 if (gs_autoIdsRefCount[winid] == ID_COUNTTOOLARGE-1)
120 {
121 // we need to allocate a cell, and maybe the hash map itself
122 if (!gs_autoIdsLargeRefCount)
123 gs_autoIdsLargeRefCount = new wxLongToLongHashMap;
124 (*gs_autoIdsLargeRefCount)[winid] = ID_COUNTTOOLARGE-1;
125
126 gs_autoIdsRefCount[winid] = ID_COUNTTOOLARGE;
127 }
128 ++(*gs_autoIdsLargeRefCount)[winid];
129 }
130 else
131 {
132 gs_autoIdsRefCount[winid]++;
133 }
134
135 wxLogTrace(wxTRACE_WINDOWID, wxT("Increasing ref count of ID %d to %d"),
136 winid + wxID_AUTO_LOWEST, GetIdRefCount(winid + wxID_AUTO_LOWEST));
137 }
138
139 // Decrease the count for an id
140 void DecIdRefCount(wxWindowID winid)
141 {
142 wxCHECK_RET(winid >= wxID_AUTO_LOWEST && winid <= wxID_AUTO_HIGHEST,
143 wxT("invalid id range"));
144
145 winid -= wxID_AUTO_LOWEST;
146
147 wxCHECK_RET(gs_autoIdsRefCount[winid] != ID_FREE, wxT("id count already 0"));
148
149 // DecIdRefCount is only called on an ID that has been IncIdRefCount'ed'
150 // so it should never be reserved, but test anyway
151 if(gs_autoIdsRefCount[winid] == ID_RESERVED)
152 {
153 wxFAIL_MSG(wxT("reserve id being decreased"));
154 gs_autoIdsRefCount[winid] = ID_FREE;
155 }
156 else if(gs_autoIdsRefCount[winid] == ID_COUNTTOOLARGE)
157 {
158 long &largeCount = (*gs_autoIdsLargeRefCount)[winid];
159 --largeCount;
160 if (largeCount == 0)
161 {
162 gs_autoIdsLargeRefCount->erase (winid);
163 gs_autoIdsRefCount[winid] = ID_FREE;
164
165 if (gs_autoIdsLargeRefCount->empty())
166 wxDELETE (gs_autoIdsLargeRefCount);
167 }
168 }
169 else
170 gs_autoIdsRefCount[winid]--;
171
172 wxLogTrace(wxTRACE_WINDOWID, wxT("Decreasing ref count of ID %d to %d"),
173 winid + wxID_AUTO_LOWEST, GetIdRefCount(winid + wxID_AUTO_LOWEST));
174 }
175
176 #else // wxUSE_AUTOID_MANAGEMENT
177
178 static wxWindowID gs_nextAutoId = wxID_AUTO_HIGHEST;
179
180 #endif
181
182 } // anonymous namespace
183
184
185 #if wxUSE_AUTOID_MANAGEMENT
186
187 void wxWindowIDRef::Assign(wxWindowID winid)
188 {
189 if ( winid != m_id )
190 {
191 // decrease count if it is in the managed range
192 if ( m_id >= wxID_AUTO_LOWEST && m_id <= wxID_AUTO_HIGHEST )
193 DecIdRefCount(m_id);
194
195 m_id = winid;
196
197 // increase count if it is in the managed range
198 if ( m_id >= wxID_AUTO_LOWEST && m_id <= wxID_AUTO_HIGHEST )
199 IncIdRefCount(m_id);
200 }
201 }
202
203 #endif // wxUSE_AUTOID_MANAGEMENT
204
205
206
207 wxWindowID wxIdManager::ReserveId(int count)
208 {
209 wxASSERT_MSG(count > 0, wxT("can't allocate less than 1 id"));
210
211
212 #if wxUSE_AUTOID_MANAGEMENT
213 if ( gs_nextAutoId + count - 1 <= wxID_AUTO_HIGHEST )
214 {
215 wxWindowID winid = gs_nextAutoId;
216
217 while(count--)
218 {
219 ReserveIdRefCount(gs_nextAutoId++);
220 }
221
222 return winid;
223 }
224 else
225 {
226 int found = 0;
227
228 for(wxWindowID winid = wxID_AUTO_LOWEST; winid <= wxID_AUTO_HIGHEST; winid++)
229 {
230 if(GetIdRefCount(winid) == 0)
231 {
232 found++;
233 if(found == count)
234 {
235 // Imagine this: 100 free IDs left. Then NewId(50) takes 50
236 // so 50 left. Then, the 25 before that last 50 are freed, but
237 // gs_nextAutoId does not decrement and stays where it is at
238 // with 50 free. Then NewId(75) gets called, and since there
239 // are only 50 left according to gs_nextAutoId, it does a
240 // search and finds the 75 at the end. Then NewId(10) gets
241 // called, and accorind to gs_nextAutoId, their are still
242 // 50 at the end so it returns them without testing the ref
243 // To fix this, the next ID is also updated here as needed
244 if(winid >= gs_nextAutoId)
245 gs_nextAutoId = winid + 1;
246
247 while(count--)
248 ReserveIdRefCount(winid--);
249
250 return winid + 1;
251 }
252 }
253 else
254 {
255 found = 0;
256 }
257 }
258 }
259
260 wxLogError(_("Out of window IDs. Recommend shutting down application."));
261 return wxID_NONE;
262 #else // !wxUSE_AUTOID_MANAGEMENT
263 // Make sure enough in the range
264 wxWindowID winid;
265
266 winid = gs_nextAutoId - count + 1;
267
268 if ( winid >= wxID_AUTO_LOWEST && winid <= wxID_AUTO_HIGHEST )
269 {
270 // There is enough, but it may be time to wrap
271 if(winid == wxID_AUTO_LOWEST)
272 gs_nextAutoId = wxID_AUTO_HIGHEST;
273 else
274 gs_nextAutoId = winid - 1;
275
276 return winid;
277 }
278 else
279 {
280 // There is not enough at the low end of the range or
281 // count was big enough to wrap around to the positive
282 // Surely 'count' is not so big to take up much of the range
283 gs_nextAutoId = wxID_AUTO_HIGHEST - count;
284 return gs_nextAutoId + 1;
285 }
286 #endif // wxUSE_AUTOID_MANAGEMENT/!wxUSE_AUTOID_MANAGEMENT
287 }
288
289 void wxIdManager::UnreserveId(wxWindowID winid, int count)
290 {
291 wxASSERT_MSG(count > 0, wxT("can't unreserve less than 1 id"));
292
293 #if wxUSE_AUTOID_MANAGEMENT
294 while (count--)
295 UnreserveIdRefCount(winid++);
296 #else
297 wxUnusedVar(winid);
298 wxUnusedVar(count);
299 #endif
300 }
301