1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : // vim:cindent:ai:sw=4:ts=4:et:
3 : /* ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is nsCounterManager.
17 : *
18 : * The Initial Developer of the Original Code is the Mozilla Foundation.
19 : * Portions created by the Initial Developer are Copyright (C) 2004
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * L. David Baron <dbaron@dbaron.org> (original author)
24 : *
25 : * Alternatively, the contents of this file may be used under the terms of
26 : * either the GNU General Public License Version 2 or later (the "GPL"), or
27 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 : * in which case the provisions of the GPL or the LGPL are applicable instead
29 : * of those above. If you wish to allow use of your version of this file only
30 : * under the terms of either the GPL or the LGPL, and not to allow others to
31 : * use your version of this file under the terms of the MPL, indicate your
32 : * decision by deleting the provisions above and replace them with the notice
33 : * and other provisions required by the GPL or the LGPL. If you do not delete
34 : * the provisions above, a recipient may use your version of this file under
35 : * the terms of any one of the MPL, the GPL or the LGPL.
36 : *
37 : * ***** END LICENSE BLOCK ***** */
38 :
39 : /* implementation of CSS counters (for numbering things) */
40 :
41 : #include "nsCounterManager.h"
42 : #include "nsBulletFrame.h" // legacy location for list style type to text code
43 : #include "nsContentUtils.h"
44 : #include "nsTArray.h"
45 :
46 : bool
47 0 : nsCounterUseNode::InitTextFrame(nsGenConList* aList,
48 : nsIFrame* aPseudoFrame, nsIFrame* aTextFrame)
49 : {
50 0 : nsCounterNode::InitTextFrame(aList, aPseudoFrame, aTextFrame);
51 :
52 0 : nsCounterList *counterList = static_cast<nsCounterList*>(aList);
53 0 : counterList->Insert(this);
54 0 : bool dirty = counterList->IsDirty();
55 0 : if (!dirty) {
56 0 : if (counterList->IsLast(this)) {
57 0 : Calc(counterList);
58 0 : nsAutoString contentString;
59 0 : GetText(contentString);
60 0 : aTextFrame->GetContent()->SetText(contentString, false);
61 : } else {
62 : // In all other cases (list already dirty or node not at the end),
63 : // just start with an empty string for now and when we recalculate
64 : // the list we'll change the value to the right one.
65 0 : counterList->SetDirty();
66 0 : return true;
67 : }
68 : }
69 :
70 0 : return false;
71 : }
72 :
73 : // assign the correct |mValueAfter| value to a node that has been inserted
74 : // Should be called immediately after calling |Insert|.
75 0 : void nsCounterUseNode::Calc(nsCounterList *aList)
76 : {
77 0 : NS_ASSERTION(!aList->IsDirty(),
78 : "Why are we calculating with a dirty list?");
79 0 : mValueAfter = aList->ValueBefore(this);
80 0 : }
81 :
82 : // assign the correct |mValueAfter| value to a node that has been inserted
83 : // Should be called immediately after calling |Insert|.
84 0 : void nsCounterChangeNode::Calc(nsCounterList *aList)
85 : {
86 0 : NS_ASSERTION(!aList->IsDirty(),
87 : "Why are we calculating with a dirty list?");
88 0 : if (mType == RESET) {
89 0 : mValueAfter = mChangeValue;
90 : } else {
91 0 : NS_ASSERTION(mType == INCREMENT, "invalid type");
92 0 : mValueAfter = aList->ValueBefore(this) + mChangeValue;
93 : }
94 0 : }
95 :
96 : // The text that should be displayed for this counter.
97 : void
98 0 : nsCounterUseNode::GetText(nsString& aResult)
99 : {
100 0 : aResult.Truncate();
101 :
102 0 : nsAutoTArray<nsCounterNode*, 8> stack;
103 0 : stack.AppendElement(static_cast<nsCounterNode*>(this));
104 :
105 0 : if (mAllCounters && mScopeStart)
106 0 : for (nsCounterNode *n = mScopeStart; n->mScopePrev; n = n->mScopeStart)
107 0 : stack.AppendElement(n->mScopePrev);
108 :
109 0 : const nsCSSValue& styleItem = mCounterStyle->Item(mAllCounters ? 2 : 1);
110 0 : PRInt32 style = styleItem.GetIntValue();
111 : const PRUnichar* separator;
112 0 : if (mAllCounters)
113 0 : separator = mCounterStyle->Item(1).GetStringBufferValue();
114 :
115 0 : for (PRUint32 i = stack.Length() - 1;; --i) {
116 0 : nsCounterNode *n = stack[i];
117 0 : nsBulletFrame::AppendCounterText(style, n->mValueAfter, aResult);
118 0 : if (i == 0)
119 : break;
120 0 : NS_ASSERTION(mAllCounters, "yikes, separator is uninitialized");
121 0 : aResult.Append(separator);
122 : }
123 0 : }
124 :
125 : void
126 0 : nsCounterList::SetScope(nsCounterNode *aNode)
127 : {
128 : // This function is responsible for setting |mScopeStart| and
129 : // |mScopePrev| (whose purpose is described in nsCounterManager.h).
130 : // We do this by starting from the node immediately preceding
131 : // |aNode| in content tree order, which is reasonably likely to be
132 : // the previous element in our scope (or, for a reset, the previous
133 : // element in the containing scope, which is what we want). If
134 : // we're not in the same scope that it is, then it's too deep in the
135 : // frame tree, so we walk up parent scopes until we find something
136 : // appropriate.
137 :
138 0 : if (aNode == First()) {
139 0 : aNode->mScopeStart = nsnull;
140 0 : aNode->mScopePrev = nsnull;
141 0 : return;
142 : }
143 :
144 : // Get the content node for aNode's rendering object's *parent*,
145 : // since scope includes siblings, so we want a descendant check on
146 : // parents.
147 0 : nsIContent *nodeContent = aNode->mPseudoFrame->GetContent()->GetParent();
148 :
149 0 : for (nsCounterNode *prev = Prev(aNode), *start;
150 : prev; prev = start->mScopePrev) {
151 : // If |prev| starts a scope (because it's a real or implied
152 : // reset), we want it as the scope start rather than the start
153 : // of its enclosing scope. Otherwise, there's no enclosing
154 : // scope, so the next thing in prev's scope shares its scope
155 : // start.
156 0 : start = (prev->mType == nsCounterNode::RESET || !prev->mScopeStart)
157 0 : ? prev : prev->mScopeStart;
158 :
159 : // |startContent| is analogous to |nodeContent| (see above).
160 0 : nsIContent *startContent = start->mPseudoFrame->GetContent()->GetParent();
161 0 : NS_ASSERTION(nodeContent || !startContent,
162 : "null check on startContent should be sufficient to "
163 : "null check nodeContent as well, since if nodeContent "
164 : "is for the root, startContent (which is before it) "
165 : "must be too");
166 :
167 : // A reset's outer scope can't be a scope created by a sibling.
168 0 : if (!(aNode->mType == nsCounterNode::RESET &&
169 0 : nodeContent == startContent) &&
170 : // everything is inside the root (except the case above,
171 : // a second reset on the root)
172 : (!startContent ||
173 : nsContentUtils::ContentIsDescendantOf(nodeContent,
174 0 : startContent))) {
175 0 : aNode->mScopeStart = start;
176 0 : aNode->mScopePrev = prev;
177 0 : return;
178 : }
179 : }
180 :
181 0 : aNode->mScopeStart = nsnull;
182 0 : aNode->mScopePrev = nsnull;
183 : }
184 :
185 : void
186 0 : nsCounterList::RecalcAll()
187 : {
188 0 : mDirty = false;
189 :
190 0 : nsCounterNode *node = First();
191 0 : if (!node)
192 0 : return;
193 :
194 0 : do {
195 0 : SetScope(node);
196 0 : node->Calc(this);
197 :
198 0 : if (node->mType == nsCounterNode::USE) {
199 0 : nsCounterUseNode *useNode = node->UseNode();
200 : // Null-check mText, since if the frame constructor isn't
201 : // batching, we could end up here while the node is being
202 : // constructed.
203 0 : if (useNode->mText) {
204 0 : nsAutoString text;
205 0 : useNode->GetText(text);
206 0 : useNode->mText->SetData(text);
207 : }
208 : }
209 0 : } while ((node = Next(node)) != First());
210 : }
211 :
212 0 : nsCounterManager::nsCounterManager()
213 : {
214 0 : mNames.Init(16);
215 0 : }
216 :
217 : bool
218 0 : nsCounterManager::AddCounterResetsAndIncrements(nsIFrame *aFrame)
219 : {
220 0 : const nsStyleContent *styleContent = aFrame->GetStyleContent();
221 0 : if (!styleContent->CounterIncrementCount() &&
222 0 : !styleContent->CounterResetCount())
223 0 : return false;
224 :
225 : // Add in order, resets first, so all the comparisons will be optimized
226 : // for addition at the end of the list.
227 : PRInt32 i, i_end;
228 0 : bool dirty = false;
229 0 : for (i = 0, i_end = styleContent->CounterResetCount(); i != i_end; ++i)
230 : dirty |= AddResetOrIncrement(aFrame, i,
231 : styleContent->GetCounterResetAt(i),
232 0 : nsCounterChangeNode::RESET);
233 0 : for (i = 0, i_end = styleContent->CounterIncrementCount(); i != i_end; ++i)
234 : dirty |= AddResetOrIncrement(aFrame, i,
235 : styleContent->GetCounterIncrementAt(i),
236 0 : nsCounterChangeNode::INCREMENT);
237 0 : return dirty;
238 : }
239 :
240 : bool
241 0 : nsCounterManager::AddResetOrIncrement(nsIFrame *aFrame, PRInt32 aIndex,
242 : const nsStyleCounterData *aCounterData,
243 : nsCounterNode::Type aType)
244 : {
245 : nsCounterChangeNode *node =
246 0 : new nsCounterChangeNode(aFrame, aType, aCounterData->mValue, aIndex);
247 :
248 0 : nsCounterList *counterList = CounterListFor(aCounterData->mCounter);
249 0 : if (!counterList) {
250 0 : NS_NOTREACHED("CounterListFor failed (should only happen on OOM)");
251 0 : return false;
252 : }
253 :
254 0 : counterList->Insert(node);
255 0 : if (!counterList->IsLast(node)) {
256 : // Tell the caller it's responsible for recalculating the entire
257 : // list.
258 0 : counterList->SetDirty();
259 0 : return true;
260 : }
261 :
262 : // Don't call Calc() if the list is already dirty -- it'll be recalculated
263 : // anyway, and trying to calculate with a dirty list doesn't work.
264 0 : if (NS_LIKELY(!counterList->IsDirty())) {
265 0 : node->Calc(counterList);
266 : }
267 0 : return false;
268 : }
269 :
270 : nsCounterList*
271 0 : nsCounterManager::CounterListFor(const nsSubstring& aCounterName)
272 : {
273 : // XXX Why doesn't nsTHashtable provide an API that allows us to use
274 : // get/put in one hashtable lookup?
275 : nsCounterList *counterList;
276 0 : if (!mNames.Get(aCounterName, &counterList)) {
277 0 : counterList = new nsCounterList();
278 0 : if (!mNames.Put(aCounterName, counterList)) {
279 0 : delete counterList;
280 0 : return nsnull;
281 : }
282 : }
283 0 : return counterList;
284 : }
285 :
286 : static PLDHashOperator
287 0 : RecalcDirtyLists(const nsAString& aKey, nsCounterList* aList, void* aClosure)
288 : {
289 0 : if (aList->IsDirty())
290 0 : aList->RecalcAll();
291 0 : return PL_DHASH_NEXT;
292 : }
293 :
294 : void
295 0 : nsCounterManager::RecalcAll()
296 : {
297 0 : mNames.EnumerateRead(RecalcDirtyLists, nsnull);
298 0 : }
299 :
300 : struct DestroyNodesData {
301 0 : DestroyNodesData(nsIFrame *aFrame)
302 : : mFrame(aFrame)
303 0 : , mDestroyedAny(false)
304 : {
305 0 : }
306 :
307 : nsIFrame *mFrame;
308 : bool mDestroyedAny;
309 : };
310 :
311 : static PLDHashOperator
312 0 : DestroyNodesInList(const nsAString& aKey, nsCounterList* aList, void* aClosure)
313 : {
314 0 : DestroyNodesData *data = static_cast<DestroyNodesData*>(aClosure);
315 0 : if (aList->DestroyNodesFor(data->mFrame)) {
316 0 : data->mDestroyedAny = true;
317 0 : aList->SetDirty();
318 : }
319 0 : return PL_DHASH_NEXT;
320 : }
321 :
322 : bool
323 0 : nsCounterManager::DestroyNodesFor(nsIFrame *aFrame)
324 : {
325 0 : DestroyNodesData data(aFrame);
326 0 : mNames.EnumerateRead(DestroyNodesInList, &data);
327 0 : return data.mDestroyedAny;
328 : }
329 :
330 : #ifdef DEBUG
331 : static PLDHashOperator
332 0 : DumpList(const nsAString& aKey, nsCounterList* aList, void* aClosure)
333 : {
334 0 : printf("Counter named \"%s\":\n", NS_ConvertUTF16toUTF8(aKey).get());
335 0 : nsCounterNode *node = aList->First();
336 :
337 0 : if (node) {
338 0 : PRInt32 i = 0;
339 0 : do {
340 0 : const char *types[] = { "RESET", "INCREMENT", "USE" };
341 : printf(" Node #%d @%p frame=%p index=%d type=%s valAfter=%d\n"
342 : " scope-start=%p scope-prev=%p",
343 : i++, (void*)node, (void*)node->mPseudoFrame,
344 : node->mContentIndex, types[node->mType], node->mValueAfter,
345 0 : (void*)node->mScopeStart, (void*)node->mScopePrev);
346 0 : if (node->mType == nsCounterNode::USE) {
347 0 : nsAutoString text;
348 0 : node->UseNode()->GetText(text);
349 0 : printf(" text=%s", NS_ConvertUTF16toUTF8(text).get());
350 : }
351 0 : printf("\n");
352 0 : } while ((node = aList->Next(node)) != aList->First());
353 : }
354 0 : return PL_DHASH_NEXT;
355 : }
356 :
357 : void
358 0 : nsCounterManager::Dump()
359 : {
360 0 : printf("\n\nCounter Manager Lists:\n");
361 0 : mNames.EnumerateRead(DumpList, nsnull);
362 0 : printf("\n\n");
363 0 : }
364 : #endif
|