1 | // SPDX-License-Identifier: MIT OR MPL-2.0 OR LGPL-2.1-or-later OR GPL-2.0-or-later |
2 | // Copyright 2010, SIL International, All rights reserved. |
3 | |
4 | #include "inc/Segment.h" |
5 | #include "inc/Slot.h" |
6 | #include "inc/Silf.h" |
7 | #include "inc/CharInfo.h" |
8 | #include "inc/Rule.h" |
9 | #include "inc/Collider.h" |
10 | |
11 | |
12 | using namespace graphite2; |
13 | |
14 | Slot::Slot(int16 *user_attrs) : |
15 | m_next(NULL), m_prev(NULL), |
16 | m_glyphid(0), m_realglyphid(0), m_original(0), m_before(0), m_after(0), |
17 | m_index(0), m_parent(NULL), m_child(NULL), m_sibling(NULL), |
18 | m_position(0, 0), m_shift(0, 0), m_advance(0, 0), |
19 | m_attach(0, 0), m_with(0, 0), m_just(0.), |
20 | m_flags(0), m_attLevel(0), m_bidiCls(-1), m_bidiLevel(0), |
21 | m_userAttr(user_attrs), m_justs(NULL) |
22 | { |
23 | } |
24 | |
25 | // take care, this does not copy any of the GrSlot pointer fields |
26 | void Slot::set(const Slot & orig, int charOffset, size_t sizeAttr, size_t justLevels, size_t numChars) |
27 | { |
28 | // leave m_next and m_prev unchanged |
29 | m_glyphid = orig.m_glyphid; |
30 | m_realglyphid = orig.m_realglyphid; |
31 | m_original = orig.m_original + charOffset; |
32 | if (charOffset + int(orig.m_before) < 0) |
33 | m_before = 0; |
34 | else |
35 | m_before = orig.m_before + charOffset; |
36 | if (charOffset <= 0 && orig.m_after + charOffset >= numChars) |
37 | m_after = int(numChars) - 1; |
38 | else |
39 | m_after = orig.m_after + charOffset; |
40 | m_parent = NULL; |
41 | m_child = NULL; |
42 | m_sibling = NULL; |
43 | m_position = orig.m_position; |
44 | m_shift = orig.m_shift; |
45 | m_advance = orig.m_advance; |
46 | m_attach = orig.m_attach; |
47 | m_with = orig.m_with; |
48 | m_flags = orig.m_flags; |
49 | m_attLevel = orig.m_attLevel; |
50 | m_bidiCls = orig.m_bidiCls; |
51 | m_bidiLevel = orig.m_bidiLevel; |
52 | if (m_userAttr && orig.m_userAttr) |
53 | memcpy(m_userAttr, orig.m_userAttr, sizeAttr * sizeof(*m_userAttr)); |
54 | if (m_justs && orig.m_justs) |
55 | memcpy(m_justs, orig.m_justs, SlotJustify::size_of(justLevels)); |
56 | } |
57 | |
58 | void Slot::update(int /*numGrSlots*/, int numCharInfo, Position &relpos) |
59 | { |
60 | m_before += numCharInfo; |
61 | m_after += numCharInfo; |
62 | m_position = m_position + relpos; |
63 | } |
64 | |
65 | Position Slot::finalise(const Segment *seg, const Font *font, Position & base, Rect & bbox, uint8 attrLevel, float & clusterMin, bool rtl, bool isFinal, int depth) |
66 | { |
67 | SlotCollision *coll = NULL; |
68 | if (depth > 100 || (attrLevel && m_attLevel > attrLevel)) return Position(0, 0); |
69 | float scale = font ? font->scale() : 1.0f; |
70 | Position shift(m_shift.x * (rtl * -2 + 1) + m_just, m_shift.y); |
71 | float tAdvance = m_advance.x + m_just; |
72 | if (isFinal && (coll = seg->collisionInfo(this))) |
73 | { |
74 | const Position &collshift = coll->offset(); |
75 | if (!(coll->flags() & SlotCollision::COLL_KERN) || rtl) |
76 | shift = shift + collshift; |
77 | } |
78 | const GlyphFace * glyphFace = seg->getFace()->glyphs().glyphSafe(glyph()); |
79 | if (font) |
80 | { |
81 | scale = font->scale(); |
82 | shift *= scale; |
83 | if (font->isHinted() && glyphFace) |
84 | tAdvance = (m_advance.x - glyphFace->theAdvance().x + m_just) * scale + font->advance(glyph()); |
85 | else |
86 | tAdvance *= scale; |
87 | } |
88 | Position res; |
89 | |
90 | m_position = base + shift; |
91 | if (!m_parent) |
92 | { |
93 | res = base + Position(tAdvance, m_advance.y * scale); |
94 | clusterMin = m_position.x; |
95 | } |
96 | else |
97 | { |
98 | float tAdv; |
99 | m_position += (m_attach - m_with) * scale; |
100 | tAdv = m_advance.x >= 0.5f ? m_position.x + tAdvance - shift.x : 0.f; |
101 | res = Position(tAdv, 0); |
102 | if ((m_advance.x >= 0.5f || m_position.x < 0) && m_position.x < clusterMin) clusterMin = m_position.x; |
103 | } |
104 | |
105 | if (glyphFace) |
106 | { |
107 | Rect ourBbox = glyphFace->theBBox() * scale + m_position; |
108 | bbox = bbox.widen(ourBbox); |
109 | } |
110 | |
111 | if (m_child && m_child != this && m_child->attachedTo() == this) |
112 | { |
113 | Position tRes = m_child->finalise(seg, font, m_position, bbox, attrLevel, clusterMin, rtl, isFinal, depth + 1); |
114 | if ((!m_parent || m_advance.x >= 0.5f) && tRes.x > res.x) res = tRes; |
115 | } |
116 | |
117 | if (m_parent && m_sibling && m_sibling != this && m_sibling->attachedTo() == m_parent) |
118 | { |
119 | Position tRes = m_sibling->finalise(seg, font, base, bbox, attrLevel, clusterMin, rtl, isFinal, depth + 1); |
120 | if (tRes.x > res.x) res = tRes; |
121 | } |
122 | |
123 | if (!m_parent && clusterMin < base.x) |
124 | { |
125 | Position adj = Position(m_position.x - clusterMin, 0.); |
126 | res += adj; |
127 | m_position += adj; |
128 | if (m_child) m_child->floodShift(adj); |
129 | } |
130 | return res; |
131 | } |
132 | |
133 | int32 Slot::clusterMetric(const Segment *seg, uint8 metric, uint8 attrLevel, bool rtl) |
134 | { |
135 | Position base; |
136 | if (glyph() >= seg->getFace()->glyphs().numGlyphs()) |
137 | return 0; |
138 | Rect bbox = seg->theGlyphBBoxTemporary(glyph()); |
139 | float clusterMin = 0.; |
140 | Position res = finalise(seg, NULL, base, bbox, attrLevel, clusterMin, rtl, false); |
141 | |
142 | switch (metrics(metric)) |
143 | { |
144 | case kgmetLsb : |
145 | return int32(bbox.bl.x); |
146 | case kgmetRsb : |
147 | return int32(res.x - bbox.tr.x); |
148 | case kgmetBbTop : |
149 | return int32(bbox.tr.y); |
150 | case kgmetBbBottom : |
151 | return int32(bbox.bl.y); |
152 | case kgmetBbLeft : |
153 | return int32(bbox.bl.x); |
154 | case kgmetBbRight : |
155 | return int32(bbox.tr.x); |
156 | case kgmetBbWidth : |
157 | return int32(bbox.tr.x - bbox.bl.x); |
158 | case kgmetBbHeight : |
159 | return int32(bbox.tr.y - bbox.bl.y); |
160 | case kgmetAdvWidth : |
161 | return int32(res.x); |
162 | case kgmetAdvHeight : |
163 | return int32(res.y); |
164 | default : |
165 | return 0; |
166 | } |
167 | } |
168 | |
169 | #define SLOTGETCOLATTR(x) { SlotCollision *c = seg->collisionInfo(this); return c ? int(c-> x) : 0; } |
170 | |
171 | int Slot::getAttr(const Segment *seg, attrCode ind, uint8 subindex) const |
172 | { |
173 | if (ind >= gr_slatJStretch && ind < gr_slatJStretch + 20 && ind != gr_slatJWidth) |
174 | { |
175 | int indx = ind - gr_slatJStretch; |
176 | return getJustify(seg, indx / 5, indx % 5); |
177 | } |
178 | |
179 | switch (ind) |
180 | { |
181 | case gr_slatAdvX : return int(m_advance.x); |
182 | case gr_slatAdvY : return int(m_advance.y); |
183 | case gr_slatAttTo : return m_parent ? 1 : 0; |
184 | case gr_slatAttX : return int(m_attach.x); |
185 | case gr_slatAttY : return int(m_attach.y); |
186 | case gr_slatAttXOff : |
187 | case gr_slatAttYOff : return 0; |
188 | case gr_slatAttWithX : return int(m_with.x); |
189 | case gr_slatAttWithY : return int(m_with.y); |
190 | case gr_slatAttWithXOff: |
191 | case gr_slatAttWithYOff:return 0; |
192 | case gr_slatAttLevel : return m_attLevel; |
193 | case gr_slatBreak : return seg->charinfo(m_original)->breakWeight(); |
194 | case gr_slatCompRef : return 0; |
195 | case gr_slatDir : return seg->dir() & 1; |
196 | case gr_slatInsert : return isInsertBefore(); |
197 | case gr_slatPosX : return int(m_position.x); // but need to calculate it |
198 | case gr_slatPosY : return int(m_position.y); |
199 | case gr_slatShiftX : return int(m_shift.x); |
200 | case gr_slatShiftY : return int(m_shift.y); |
201 | case gr_slatMeasureSol: return -1; // err what's this? |
202 | case gr_slatMeasureEol: return -1; |
203 | case gr_slatJWidth: return int(m_just); |
204 | case gr_slatUserDefnV1: subindex = 0; GR_FALLTHROUGH; |
205 | // no break |
206 | case gr_slatUserDefn : return subindex < seg->numAttrs() ? m_userAttr[subindex] : 0; |
207 | case gr_slatSegSplit : return seg->charinfo(m_original)->flags() & 3; |
208 | case gr_slatBidiLevel: return m_bidiLevel; |
209 | case gr_slatColFlags : { SlotCollision *c = seg->collisionInfo(this); return c ? c->flags() : 0; } |
210 | case gr_slatColLimitblx:SLOTGETCOLATTR(limit().bl.x) |
211 | case gr_slatColLimitbly:SLOTGETCOLATTR(limit().bl.y) |
212 | case gr_slatColLimittrx:SLOTGETCOLATTR(limit().tr.x) |
213 | case gr_slatColLimittry:SLOTGETCOLATTR(limit().tr.y) |
214 | case gr_slatColShiftx : SLOTGETCOLATTR(offset().x) |
215 | case gr_slatColShifty : SLOTGETCOLATTR(offset().y) |
216 | case gr_slatColMargin : SLOTGETCOLATTR(margin()) |
217 | case gr_slatColMarginWt:SLOTGETCOLATTR(marginWt()) |
218 | case gr_slatColExclGlyph:SLOTGETCOLATTR(exclGlyph()) |
219 | case gr_slatColExclOffx:SLOTGETCOLATTR(exclOffset().x) |
220 | case gr_slatColExclOffy:SLOTGETCOLATTR(exclOffset().y) |
221 | case gr_slatSeqClass : SLOTGETCOLATTR(seqClass()) |
222 | case gr_slatSeqProxClass:SLOTGETCOLATTR(seqProxClass()) |
223 | case gr_slatSeqOrder : SLOTGETCOLATTR(seqOrder()) |
224 | case gr_slatSeqAboveXoff:SLOTGETCOLATTR(seqAboveXoff()) |
225 | case gr_slatSeqAboveWt: SLOTGETCOLATTR(seqAboveWt()) |
226 | case gr_slatSeqBelowXlim:SLOTGETCOLATTR(seqBelowXlim()) |
227 | case gr_slatSeqBelowWt: SLOTGETCOLATTR(seqBelowWt()) |
228 | case gr_slatSeqValignHt:SLOTGETCOLATTR(seqValignHt()) |
229 | case gr_slatSeqValignWt:SLOTGETCOLATTR(seqValignWt()) |
230 | default : return 0; |
231 | } |
232 | } |
233 | |
234 | #define SLOTCOLSETATTR(x) { \ |
235 | SlotCollision *c = seg->collisionInfo(this); \ |
236 | if (c) { c-> x ; c->setFlags(c->flags() & ~SlotCollision::COLL_KNOWN); } \ |
237 | break; } |
238 | #define SLOTCOLSETCOMPLEXATTR(t, y, x) { \ |
239 | SlotCollision *c = seg->collisionInfo(this); \ |
240 | if (c) { \ |
241 | const t &s = c-> y; \ |
242 | c-> x ; c->setFlags(c->flags() & ~SlotCollision::COLL_KNOWN); } \ |
243 | break; } |
244 | |
245 | void Slot::setAttr(Segment *seg, attrCode ind, uint8 subindex, int16 value, const SlotMap & map) |
246 | { |
247 | if (ind == gr_slatUserDefnV1) |
248 | { |
249 | ind = gr_slatUserDefn; |
250 | subindex = 0; |
251 | if (seg->numAttrs() == 0) |
252 | return; |
253 | } |
254 | else if (ind >= gr_slatJStretch && ind < gr_slatJStretch + 20 && ind != gr_slatJWidth) |
255 | { |
256 | int indx = ind - gr_slatJStretch; |
257 | return setJustify(seg, indx / 5, indx % 5, value); |
258 | } |
259 | |
260 | switch (ind) |
261 | { |
262 | case gr_slatAdvX : m_advance.x = value; break; |
263 | case gr_slatAdvY : m_advance.y = value; break; |
264 | case gr_slatAttTo : |
265 | { |
266 | const uint16 idx = uint16(value); |
267 | if (idx < map.size() && map[idx]) |
268 | { |
269 | Slot *other = map[idx]; |
270 | if (other == this || other == m_parent || other->isCopied()) break; |
271 | if (m_parent) { m_parent->removeChild(this); attachTo(NULL); } |
272 | Slot *pOther = other; |
273 | int count = 0; |
274 | bool foundOther = false; |
275 | while (pOther) |
276 | { |
277 | ++count; |
278 | if (pOther == this) foundOther = true; |
279 | pOther = pOther->attachedTo(); |
280 | } |
281 | for (pOther = m_child; pOther; pOther = pOther->m_child) |
282 | ++count; |
283 | for (pOther = m_sibling; pOther; pOther = pOther->m_sibling) |
284 | ++count; |
285 | if (count < 100 && !foundOther && other->child(this)) |
286 | { |
287 | attachTo(other); |
288 | if ((map.dir() != 0) ^ (idx > subindex)) |
289 | m_with = Position(advance(), 0); |
290 | else // normal match to previous root |
291 | m_attach = Position(other->advance(), 0); |
292 | } |
293 | } |
294 | break; |
295 | } |
296 | case gr_slatAttX : m_attach.x = value; break; |
297 | case gr_slatAttY : m_attach.y = value; break; |
298 | case gr_slatAttXOff : |
299 | case gr_slatAttYOff : break; |
300 | case gr_slatAttWithX : m_with.x = value; break; |
301 | case gr_slatAttWithY : m_with.y = value; break; |
302 | case gr_slatAttWithXOff : |
303 | case gr_slatAttWithYOff : break; |
304 | case gr_slatAttLevel : |
305 | m_attLevel = byte(value); |
306 | break; |
307 | case gr_slatBreak : |
308 | seg->charinfo(m_original)->breakWeight(value); |
309 | break; |
310 | case gr_slatCompRef : break; // not sure what to do here |
311 | case gr_slatDir : break; |
312 | case gr_slatInsert : |
313 | markInsertBefore(value? true : false); |
314 | break; |
315 | case gr_slatPosX : break; // can't set these here |
316 | case gr_slatPosY : break; |
317 | case gr_slatShiftX : m_shift.x = value; break; |
318 | case gr_slatShiftY : m_shift.y = value; break; |
319 | case gr_slatMeasureSol : break; |
320 | case gr_slatMeasureEol : break; |
321 | case gr_slatJWidth : just(value); break; |
322 | case gr_slatSegSplit : seg->charinfo(m_original)->addflags(value & 3); break; |
323 | case gr_slatUserDefn : m_userAttr[subindex] = value; break; |
324 | case gr_slatColFlags : { |
325 | SlotCollision *c = seg->collisionInfo(this); |
326 | if (c) |
327 | c->setFlags(value); |
328 | break; } |
329 | case gr_slatColLimitblx : SLOTCOLSETCOMPLEXATTR(Rect, limit(), setLimit(Rect(Position(value, s.bl.y), s.tr))) |
330 | case gr_slatColLimitbly : SLOTCOLSETCOMPLEXATTR(Rect, limit(), setLimit(Rect(Position(s.bl.x, value), s.tr))) |
331 | case gr_slatColLimittrx : SLOTCOLSETCOMPLEXATTR(Rect, limit(), setLimit(Rect(s.bl, Position(value, s.tr.y)))) |
332 | case gr_slatColLimittry : SLOTCOLSETCOMPLEXATTR(Rect, limit(), setLimit(Rect(s.bl, Position(s.tr.x, value)))) |
333 | case gr_slatColMargin : SLOTCOLSETATTR(setMargin(value)) |
334 | case gr_slatColMarginWt : SLOTCOLSETATTR(setMarginWt(value)) |
335 | case gr_slatColExclGlyph : SLOTCOLSETATTR(setExclGlyph(value)) |
336 | case gr_slatColExclOffx : SLOTCOLSETCOMPLEXATTR(Position, exclOffset(), setExclOffset(Position(value, s.y))) |
337 | case gr_slatColExclOffy : SLOTCOLSETCOMPLEXATTR(Position, exclOffset(), setExclOffset(Position(s.x, value))) |
338 | case gr_slatSeqClass : SLOTCOLSETATTR(setSeqClass(value)) |
339 | case gr_slatSeqProxClass : SLOTCOLSETATTR(setSeqProxClass(value)) |
340 | case gr_slatSeqOrder : SLOTCOLSETATTR(setSeqOrder(value)) |
341 | case gr_slatSeqAboveXoff : SLOTCOLSETATTR(setSeqAboveXoff(value)) |
342 | case gr_slatSeqAboveWt : SLOTCOLSETATTR(setSeqAboveWt(value)) |
343 | case gr_slatSeqBelowXlim : SLOTCOLSETATTR(setSeqBelowXlim(value)) |
344 | case gr_slatSeqBelowWt : SLOTCOLSETATTR(setSeqBelowWt(value)) |
345 | case gr_slatSeqValignHt : SLOTCOLSETATTR(setSeqValignHt(value)) |
346 | case gr_slatSeqValignWt : SLOTCOLSETATTR(setSeqValignWt(value)) |
347 | default : |
348 | break; |
349 | } |
350 | } |
351 | |
352 | int Slot::getJustify(const Segment *seg, uint8 level, uint8 subindex) const |
353 | { |
354 | if (level && level >= seg->silf()->numJustLevels()) return 0; |
355 | |
356 | if (m_justs) |
357 | return m_justs->values[level * SlotJustify::NUMJUSTPARAMS + subindex]; |
358 | |
359 | if (level >= seg->silf()->numJustLevels()) return 0; |
360 | Justinfo *jAttrs = seg->silf()->justAttrs() + level; |
361 | |
362 | switch (subindex) { |
363 | case 0 : return seg->glyphAttr(gid(), jAttrs->attrStretch()); |
364 | case 1 : return seg->glyphAttr(gid(), jAttrs->attrShrink()); |
365 | case 2 : return seg->glyphAttr(gid(), jAttrs->attrStep()); |
366 | case 3 : return seg->glyphAttr(gid(), jAttrs->attrWeight()); |
367 | case 4 : return 0; // not been set yet, so clearly 0 |
368 | default: return 0; |
369 | } |
370 | } |
371 | |
372 | void Slot::setJustify(Segment *seg, uint8 level, uint8 subindex, int16 value) |
373 | { |
374 | if (level && level >= seg->silf()->numJustLevels()) return; |
375 | if (!m_justs) |
376 | { |
377 | SlotJustify *j = seg->newJustify(); |
378 | if (!j) return; |
379 | j->LoadSlot(this, seg); |
380 | m_justs = j; |
381 | } |
382 | m_justs->values[level * SlotJustify::NUMJUSTPARAMS + subindex] = value; |
383 | } |
384 | |
385 | bool Slot::child(Slot *ap) |
386 | { |
387 | if (this == ap) return false; |
388 | else if (ap == m_child) return true; |
389 | else if (!m_child) |
390 | m_child = ap; |
391 | else |
392 | return m_child->sibling(ap); |
393 | return true; |
394 | } |
395 | |
396 | bool Slot::sibling(Slot *ap) |
397 | { |
398 | if (this == ap) return false; |
399 | else if (ap == m_sibling) return true; |
400 | else if (!m_sibling || !ap) |
401 | m_sibling = ap; |
402 | else |
403 | return m_sibling->sibling(ap); |
404 | return true; |
405 | } |
406 | |
407 | bool Slot::removeChild(Slot *ap) |
408 | { |
409 | if (this == ap || !m_child || !ap) return false; |
410 | else if (ap == m_child) |
411 | { |
412 | Slot *nSibling = m_child->nextSibling(); |
413 | m_child->nextSibling(NULL); |
414 | m_child = nSibling; |
415 | return true; |
416 | } |
417 | for (Slot *p = m_child; p; p = p->m_sibling) |
418 | { |
419 | if (p->m_sibling && p->m_sibling == ap) |
420 | { |
421 | p->m_sibling = p->m_sibling->m_sibling; |
422 | ap->nextSibling(NULL); |
423 | return true; |
424 | } |
425 | } |
426 | return false; |
427 | } |
428 | |
429 | void Slot::setGlyph(Segment *seg, uint16 glyphid, const GlyphFace * theGlyph) |
430 | { |
431 | m_glyphid = glyphid; |
432 | m_bidiCls = -1; |
433 | if (!theGlyph) |
434 | { |
435 | theGlyph = seg->getFace()->glyphs().glyphSafe(glyphid); |
436 | if (!theGlyph) |
437 | { |
438 | m_realglyphid = 0; |
439 | m_advance = Position(0.,0.); |
440 | return; |
441 | } |
442 | } |
443 | m_realglyphid = theGlyph->attrs()[seg->silf()->aPseudo()]; |
444 | if (m_realglyphid > seg->getFace()->glyphs().numGlyphs()) |
445 | m_realglyphid = 0; |
446 | const GlyphFace *aGlyph = theGlyph; |
447 | if (m_realglyphid) |
448 | { |
449 | aGlyph = seg->getFace()->glyphs().glyphSafe(m_realglyphid); |
450 | if (!aGlyph) aGlyph = theGlyph; |
451 | } |
452 | m_advance = Position(aGlyph->theAdvance().x, 0.); |
453 | if (seg->silf()->aPassBits()) |
454 | { |
455 | seg->mergePassBits(uint8(theGlyph->attrs()[seg->silf()->aPassBits()])); |
456 | if (seg->silf()->numPasses() > 16) |
457 | seg->mergePassBits(theGlyph->attrs()[seg->silf()->aPassBits()+1] << 16); |
458 | } |
459 | } |
460 | |
461 | void Slot::floodShift(Position adj, int depth) |
462 | { |
463 | if (depth > 100) |
464 | return; |
465 | m_position += adj; |
466 | if (m_child) m_child->floodShift(adj, depth + 1); |
467 | if (m_sibling) m_sibling->floodShift(adj, depth + 1); |
468 | } |
469 | |
470 | void SlotJustify::LoadSlot(const Slot *s, const Segment *seg) |
471 | { |
472 | for (int i = seg->silf()->numJustLevels() - 1; i >= 0; --i) |
473 | { |
474 | Justinfo *justs = seg->silf()->justAttrs() + i; |
475 | int16 *v = values + i * NUMJUSTPARAMS; |
476 | v[0] = seg->glyphAttr(s->gid(), justs->attrStretch()); |
477 | v[1] = seg->glyphAttr(s->gid(), justs->attrShrink()); |
478 | v[2] = seg->glyphAttr(s->gid(), justs->attrStep()); |
479 | v[3] = seg->glyphAttr(s->gid(), justs->attrWeight()); |
480 | } |
481 | } |
482 | |
483 | Slot * Slot::nextInCluster(const Slot *s) const |
484 | { |
485 | Slot *base; |
486 | if (s->firstChild()) |
487 | return s->firstChild(); |
488 | else if (s->nextSibling()) |
489 | return s->nextSibling(); |
490 | while ((base = s->attachedTo())) |
491 | { |
492 | // if (base->firstChild() == s && base->nextSibling()) |
493 | if (base->nextSibling()) |
494 | return base->nextSibling(); |
495 | s = base; |
496 | } |
497 | return NULL; |
498 | } |
499 | |
500 | bool Slot::isChildOf(const Slot *base) const |
501 | { |
502 | for (Slot *p = m_parent; p; p = p->m_parent) |
503 | if (p == base) |
504 | return true; |
505 | return false; |
506 | } |
507 | |