nsTreeColumns.cpp (13642B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "nsTreeColumns.h" 8 9 #include "mozilla/CSSOrderAwareFrameIterator.h" 10 #include "mozilla/ComputedStyle.h" 11 #include "mozilla/dom/Element.h" 12 #include "mozilla/dom/TreeColumnBinding.h" 13 #include "mozilla/dom/TreeColumnsBinding.h" 14 #include "mozilla/dom/XULTreeElement.h" 15 #include "nsContentUtils.h" 16 #include "nsGkAtoms.h" 17 #include "nsNameSpaceManager.h" 18 #include "nsTreeBodyFrame.h" 19 #include "nsTreeUtils.h" 20 21 using namespace mozilla; 22 using namespace mozilla::dom; 23 24 // Column class that caches all the info about our column. 25 nsTreeColumn::nsTreeColumn(nsTreeColumns* aColumns, dom::Element* aElement) 26 : mContent(aElement), mColumns(aColumns), mIndex(0), mPrevious(nullptr) { 27 NS_ASSERTION(aElement && aElement->NodeInfo()->Equals(nsGkAtoms::treecol, 28 kNameSpaceID_XUL), 29 "nsTreeColumn's content must be a <xul:treecol>"); 30 31 Invalidate(IgnoreErrors()); 32 } 33 34 nsTreeColumn::~nsTreeColumn() { 35 if (mNext) { 36 mNext->SetPrevious(nullptr); 37 } 38 } 39 40 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(nsTreeColumn) 41 42 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsTreeColumn) 43 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER 44 NS_IMPL_CYCLE_COLLECTION_UNLINK(mContent) 45 if (tmp->mNext) { 46 tmp->mNext->SetPrevious(nullptr); 47 NS_IMPL_CYCLE_COLLECTION_UNLINK(mNext) 48 } 49 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 50 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsTreeColumn) 51 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContent) 52 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNext) 53 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 54 55 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTreeColumn) 56 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTreeColumn) 57 58 // QueryInterface implementation for nsTreeColumn 59 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTreeColumn) 60 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 61 NS_INTERFACE_MAP_ENTRY(nsISupports) 62 NS_INTERFACE_MAP_ENTRY_CONCRETE(nsTreeColumn) 63 NS_INTERFACE_MAP_END 64 65 nsIFrame* nsTreeColumn::GetFrame() { return mContent->GetPrimaryFrame(); } 66 67 bool nsTreeColumn::IsLastVisible(nsTreeBodyFrame* aBodyFrame) { 68 NS_ASSERTION(GetFrame(), "should have checked for this already"); 69 70 // cyclers are fixed width, don't adjust them 71 if (IsCycler()) { 72 return false; 73 } 74 75 // we're certainly not the last visible if we're not visible 76 if (GetFrame()->GetRect().width == 0) { 77 return false; 78 } 79 80 // try to find a visible successor 81 for (nsTreeColumn* next = GetNext(); next; next = next->GetNext()) { 82 nsIFrame* frame = next->GetFrame(); 83 if (frame && frame->GetRect().width > 0) { 84 return false; 85 } 86 } 87 return true; 88 } 89 90 nsresult nsTreeColumn::GetRect(nsTreeBodyFrame* aBodyFrame, nscoord aY, 91 nscoord aHeight, nsRect* aResult) { 92 nsIFrame* frame = GetFrame(); 93 if (!frame) { 94 *aResult = nsRect(); 95 return NS_ERROR_FAILURE; 96 } 97 98 *aResult = frame->GetRect(); 99 if (frame->StyleVisibility()->IsCollapse()) { 100 aResult->SizeTo(nsSize()); 101 } 102 aResult->y = aY; 103 aResult->height = aHeight; 104 return NS_OK; 105 } 106 107 nsresult nsTreeColumn::GetXInTwips(nsTreeBodyFrame* aBodyFrame, 108 nscoord* aResult) { 109 nsIFrame* frame = GetFrame(); 110 if (!frame) { 111 *aResult = 0; 112 return NS_ERROR_FAILURE; 113 } 114 *aResult = frame->GetRect().x; 115 return NS_OK; 116 } 117 118 nsresult nsTreeColumn::GetWidthInTwips(nsTreeBodyFrame* aBodyFrame, 119 nscoord* aResult) { 120 nsIFrame* frame = GetFrame(); 121 if (!frame) { 122 *aResult = 0; 123 return NS_ERROR_FAILURE; 124 } 125 *aResult = frame->GetRect().width; 126 return NS_OK; 127 } 128 129 void nsTreeColumn::GetId(nsAString& aId) const { aId = GetId(); } 130 131 void nsTreeColumn::Invalidate(ErrorResult& aRv) { 132 nsIFrame* frame = GetFrame(); 133 if (NS_WARN_IF(!frame)) { 134 aRv.Throw(NS_ERROR_FAILURE); 135 return; 136 } 137 138 // Fetch the Id. 139 mContent->GetAttr(nsGkAtoms::id, mId); 140 141 // If we have an Id, cache the Id as an atom. 142 if (!mId.IsEmpty()) { 143 mAtom = NS_Atomize(mId); 144 } 145 146 // Cache our index. 147 nsTreeUtils::GetColumnIndex(mContent, &mIndex); 148 149 const nsStyleVisibility* vis = frame->StyleVisibility(); 150 151 // Cache our text alignment policy. 152 const nsStyleText* textStyle = frame->StyleText(); 153 154 mTextAlignment = textStyle->mTextAlign; 155 // START or END alignment sometimes means RIGHT 156 if ((mTextAlignment == StyleTextAlign::Start && 157 vis->mDirection == StyleDirection::Rtl) || 158 (mTextAlignment == StyleTextAlign::End && 159 vis->mDirection == StyleDirection::Ltr)) { 160 mTextAlignment = StyleTextAlign::Right; 161 } else if (mTextAlignment == StyleTextAlign::Start || 162 mTextAlignment == StyleTextAlign::End) { 163 mTextAlignment = StyleTextAlign::Left; 164 } 165 166 // Figure out if we're the primary column (that has to have indentation 167 // and twisties drawn. 168 mIsPrimary = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::primary, 169 nsGkAtoms::_true, eCaseMatters); 170 171 // Figure out if we're a cycling column (one that doesn't cause a selection 172 // to happen). 173 mIsCycler = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::cycler, 174 nsGkAtoms::_true, eCaseMatters); 175 176 mIsEditable = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::editable, 177 nsGkAtoms::_true, eCaseMatters); 178 179 mOverflow = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::overflow, 180 nsGkAtoms::_true, eCaseMatters); 181 182 // Figure out our column type. Default type is text. 183 mType = TreeColumn_Binding::TYPE_TEXT; 184 static Element::AttrValuesArray typestrings[] = {nsGkAtoms::checkbox, 185 nullptr}; 186 switch (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::type, 187 typestrings, eCaseMatters)) { 188 case 0: 189 mType = TreeColumn_Binding::TYPE_CHECKBOX; 190 break; 191 } 192 193 // Fetch the crop style. 194 mCropStyle = 0; 195 static Element::AttrValuesArray cropstrings[] = { 196 nsGkAtoms::center, nsGkAtoms::left, nsGkAtoms::start, nullptr}; 197 switch (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::crop, 198 cropstrings, eCaseMatters)) { 199 case 0: 200 mCropStyle = 1; 201 break; 202 case 1: 203 case 2: 204 mCropStyle = 2; 205 break; 206 } 207 } 208 209 nsIContent* nsTreeColumn::GetParentObject() const { return mContent; } 210 211 /* virtual */ 212 JSObject* nsTreeColumn::WrapObject(JSContext* aCx, 213 JS::Handle<JSObject*> aGivenProto) { 214 return dom::TreeColumn_Binding::Wrap(aCx, this, aGivenProto); 215 } 216 217 Element* nsTreeColumn::Element() { return mContent; } 218 219 int32_t nsTreeColumn::GetX(mozilla::ErrorResult& aRv) { 220 nsIFrame* frame = GetFrame(); 221 if (NS_WARN_IF(!frame)) { 222 aRv.Throw(NS_ERROR_FAILURE); 223 return 0; 224 } 225 226 return nsPresContext::AppUnitsToIntCSSPixels(frame->GetRect().x); 227 } 228 229 int32_t nsTreeColumn::GetWidth(mozilla::ErrorResult& aRv) { 230 nsIFrame* frame = GetFrame(); 231 if (NS_WARN_IF(!frame)) { 232 aRv.Throw(NS_ERROR_FAILURE); 233 return 0; 234 } 235 236 return nsPresContext::AppUnitsToIntCSSPixels(frame->GetRect().width); 237 } 238 239 already_AddRefed<nsTreeColumn> nsTreeColumn::GetPreviousColumn() { 240 return do_AddRef(mPrevious); 241 } 242 243 nsTreeColumns::nsTreeColumns(nsTreeBodyFrame* aTree) : mTree(aTree) {} 244 245 nsTreeColumns::~nsTreeColumns() { nsTreeColumns::InvalidateColumns(); } 246 247 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(nsTreeColumns) 248 249 // QueryInterface implementation for nsTreeColumns 250 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTreeColumns) 251 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 252 NS_INTERFACE_MAP_ENTRY(nsISupports) 253 NS_INTERFACE_MAP_END 254 255 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTreeColumns) 256 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTreeColumns) 257 258 nsIContent* nsTreeColumns::GetParentObject() const { 259 return mTree ? mTree->GetBaseElement() : nullptr; 260 } 261 262 /* virtual */ 263 JSObject* nsTreeColumns::WrapObject(JSContext* aCx, 264 JS::Handle<JSObject*> aGivenProto) { 265 return dom::TreeColumns_Binding::Wrap(aCx, this, aGivenProto); 266 } 267 268 XULTreeElement* nsTreeColumns::GetTree() const { 269 if (!mTree) { 270 return nullptr; 271 } 272 273 return XULTreeElement::FromNodeOrNull(mTree->GetBaseElement()); 274 } 275 276 uint32_t nsTreeColumns::Count() { 277 EnsureColumns(); 278 uint32_t count = 0; 279 for (nsTreeColumn* currCol = mFirstColumn; currCol; 280 currCol = currCol->GetNext()) { 281 ++count; 282 } 283 return count; 284 } 285 286 nsTreeColumn* nsTreeColumns::GetLastColumn() { 287 EnsureColumns(); 288 nsTreeColumn* currCol = mFirstColumn; 289 while (currCol) { 290 nsTreeColumn* next = currCol->GetNext(); 291 if (!next) { 292 return currCol; 293 } 294 currCol = next; 295 } 296 return nullptr; 297 } 298 299 nsTreeColumn* nsTreeColumns::GetSortedColumn() { 300 EnsureColumns(); 301 for (nsTreeColumn* currCol = mFirstColumn; currCol; 302 currCol = currCol->GetNext()) { 303 if (nsContentUtils::HasNonEmptyAttr(currCol->mContent, kNameSpaceID_None, 304 nsGkAtoms::sortDirection)) { 305 return currCol; 306 } 307 } 308 return nullptr; 309 } 310 311 nsTreeColumn* nsTreeColumns::GetKeyColumn() { 312 EnsureColumns(); 313 314 nsTreeColumn* first = nullptr; 315 nsTreeColumn* primary = nullptr; 316 nsTreeColumn* sorted = nullptr; 317 318 for (nsTreeColumn* currCol = mFirstColumn; currCol; 319 currCol = currCol->GetNext()) { 320 // Skip hidden columns. 321 if (currCol->mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden, 322 nsGkAtoms::_true, eCaseMatters)) { 323 continue; 324 } 325 326 // Skip non-text column 327 if (currCol->GetType() != TreeColumn_Binding::TYPE_TEXT) { 328 continue; 329 } 330 331 if (!first) { 332 first = currCol; 333 } 334 335 if (nsContentUtils::HasNonEmptyAttr(currCol->mContent, kNameSpaceID_None, 336 nsGkAtoms::sortDirection)) { 337 // Use sorted column as the key. 338 sorted = currCol; 339 break; 340 } 341 342 if (currCol->IsPrimary()) { 343 if (!primary) { 344 primary = currCol; 345 } 346 } 347 } 348 349 if (sorted) { 350 return sorted; 351 } 352 if (primary) { 353 return primary; 354 } 355 return first; 356 } 357 358 nsTreeColumn* nsTreeColumns::GetColumnFor(dom::Element* aElement) { 359 EnsureColumns(); 360 for (nsTreeColumn* currCol = mFirstColumn; currCol; 361 currCol = currCol->GetNext()) { 362 if (currCol->mContent == aElement) { 363 return currCol; 364 } 365 } 366 return nullptr; 367 } 368 369 nsTreeColumn* nsTreeColumns::NamedGetter(const nsAString& aId, bool& aFound) { 370 EnsureColumns(); 371 for (nsTreeColumn* currCol = mFirstColumn; currCol; 372 currCol = currCol->GetNext()) { 373 if (currCol->GetId().Equals(aId)) { 374 aFound = true; 375 return currCol; 376 } 377 } 378 aFound = false; 379 return nullptr; 380 } 381 382 nsTreeColumn* nsTreeColumns::GetNamedColumn(const nsAString& aId) { 383 bool dummy; 384 return NamedGetter(aId, dummy); 385 } 386 387 void nsTreeColumns::GetSupportedNames(nsTArray<nsString>& aNames) { 388 for (nsTreeColumn* currCol = mFirstColumn; currCol; 389 currCol = currCol->GetNext()) { 390 aNames.AppendElement(currCol->GetId()); 391 } 392 } 393 394 nsTreeColumn* nsTreeColumns::IndexedGetter(uint32_t aIndex, bool& aFound) { 395 EnsureColumns(); 396 for (nsTreeColumn* currCol = mFirstColumn; currCol; 397 currCol = currCol->GetNext()) { 398 if (currCol->GetIndex() == static_cast<int32_t>(aIndex)) { 399 aFound = true; 400 return currCol; 401 } 402 } 403 aFound = false; 404 return nullptr; 405 } 406 407 nsTreeColumn* nsTreeColumns::GetColumnAt(uint32_t aIndex) { 408 bool dummy; 409 return IndexedGetter(aIndex, dummy); 410 } 411 412 void nsTreeColumns::InvalidateColumns() { 413 for (nsTreeColumn* currCol = mFirstColumn; currCol; 414 currCol = currCol->GetNext()) { 415 currCol->SetColumns(nullptr); 416 } 417 mFirstColumn = nullptr; 418 } 419 420 nsTreeColumn* nsTreeColumns::GetPrimaryColumn() { 421 EnsureColumns(); 422 for (nsTreeColumn* currCol = mFirstColumn; currCol; 423 currCol = currCol->GetNext()) { 424 if (currCol->IsPrimary()) { 425 return currCol; 426 } 427 } 428 return nullptr; 429 } 430 431 void nsTreeColumns::EnsureColumns() { 432 if (mTree && !mFirstColumn) { 433 nsIContent* treeContent = mTree->GetBaseElement(); 434 if (!treeContent) { 435 return; 436 } 437 438 nsIContent* colsContent = 439 nsTreeUtils::GetDescendantChild(treeContent, nsGkAtoms::treecols); 440 if (!colsContent) { 441 return; 442 } 443 444 nsIContent* colContent = 445 nsTreeUtils::GetDescendantChild(colsContent, nsGkAtoms::treecol); 446 if (!colContent) { 447 return; 448 } 449 450 nsIFrame* colFrame = colContent->GetPrimaryFrame(); 451 if (!colFrame) { 452 return; 453 } 454 455 colFrame = colFrame->GetParent(); 456 if (!colFrame || !colFrame->GetContent()) { 457 return; 458 } 459 460 nsTreeColumn* currCol = nullptr; 461 462 // Enumerate the columns in visible order 463 CSSOrderAwareFrameIterator iter( 464 colFrame, FrameChildListID::Principal, 465 CSSOrderAwareFrameIterator::ChildFilter::IncludeAll); 466 for (; !iter.AtEnd(); iter.Next()) { 467 nsIFrame* colFrame = iter.get(); 468 nsIContent* colContent = colFrame->GetContent(); 469 if (!colContent->IsXULElement(nsGkAtoms::treecol)) { 470 continue; 471 } 472 // Create a new column structure. 473 nsTreeColumn* col = new nsTreeColumn(this, colContent->AsElement()); 474 475 if (currCol) { 476 currCol->SetNext(col); 477 col->SetPrevious(currCol); 478 } else { 479 mFirstColumn = col; 480 } 481 currCol = col; 482 } 483 } 484 }