001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.gwt.client.util; 029 030import org.opencms.gwt.client.util.CmsDomUtil.Style; 031 032import com.google.gwt.dom.client.Element; 033import com.google.gwt.dom.client.Style.Display; 034import com.google.gwt.dom.client.Style.Position; 035import com.google.gwt.user.client.Window; 036import com.google.gwt.user.client.ui.UIObject; 037 038/** 039 * Bean holding the position data of a HTML DOM element.<p> 040 * 041 * @since 8.0.0 042 */ 043public class CmsPositionBean { 044 045 /** Position area. */ 046 public static enum Area { 047 048 /** Bottom border. */ 049 BORDER_BOTTOM, 050 051 /** Left border. */ 052 BORDER_LEFT, 053 054 /** Right border. */ 055 BORDER_RIGHT, 056 057 /** Top border. */ 058 BORDER_TOP, 059 060 /** The center. */ 061 CENTER, 062 063 /** Bottom left corner. */ 064 CORNER_BOTTOM_LEFT, 065 066 /** Bottom right corner. */ 067 CORNER_BOTTOM_RIGHT, 068 069 /** Top left corner. */ 070 CORNER_TOP_LEFT, 071 072 /** Top right corner. */ 073 CORNER_TOP_RIGHT 074 } 075 076 /** The directions. */ 077 static enum Direction { 078 /** Bottom. */ 079 bottom, 080 081 /** Left. */ 082 left, 083 084 /** Right. */ 085 086 right, 087 088 /** Top. */ 089 top 090 } 091 092 /** Element height. */ 093 private int m_height; 094 095 /** Position left. */ 096 private int m_left; 097 098 /** Position top. */ 099 private int m_top; 100 101 /** Element width. */ 102 private int m_width; 103 104 /** 105 * Constructor.<p> 106 */ 107 public CmsPositionBean() { 108 109 // default constructor 110 } 111 112 /** 113 * Copy constructor. Generating a copy of the given model.<p> 114 * 115 * @param model the model to copy 116 */ 117 public CmsPositionBean(CmsPositionBean model) { 118 119 m_height = model.getHeight(); 120 m_left = model.getLeft(); 121 m_top = model.getTop(); 122 m_width = model.getWidth(); 123 } 124 125 /** 126 * Manipulates the position infos to ensure a minimum margin between the rectangles.<p> 127 * 128 * @param posA the first position to check 129 * @param posB the second position to check 130 * @param margin the required margin 131 */ 132 public static void avoidCollision(CmsPositionBean posA, CmsPositionBean posB, int margin) { 133 134 Direction dir = null; 135 int diff = 0; 136 int diffTemp = (posB.getLeft() + posB.getWidth()) - posA.getLeft(); 137 if (diffTemp > -margin) { 138 dir = Direction.left; 139 diff = diffTemp; 140 } 141 142 diffTemp = (posA.getLeft() + posA.getWidth()) - posB.getLeft(); 143 if ((diffTemp > -margin) && (diffTemp < diff)) { 144 dir = Direction.right; 145 diff = diffTemp; 146 } 147 diffTemp = (posB.getTop() + posB.getHeight()) - posA.getTop(); 148 if ((diffTemp > -margin) && (diffTemp < diff)) { 149 dir = Direction.top; 150 diff = diffTemp; 151 } 152 diffTemp = (posA.getTop() + posA.getHeight()) - posB.getTop(); 153 if ((diffTemp > -margin) && (diffTemp < diff)) { 154 dir = Direction.bottom; 155 diff = diffTemp; 156 } 157 158 diff = (int)Math.ceil((1.0 * (diff + margin)) / 2); 159 if (dir != null) { 160 switch (dir) { 161 case left: 162 // move the left border of a 163 posA.setLeft(posA.getLeft() + diff); 164 posA.setWidth(posA.getWidth() - diff); 165 166 // move the right border of b 167 posB.setWidth(posB.getWidth() - diff); 168 break; 169 170 case right: 171 // move the left border of b 172 posB.setLeft(posB.getLeft() + diff); 173 posB.setWidth(posB.getWidth() - diff); 174 175 // move the right border of a 176 posA.setWidth(posA.getWidth() - diff); 177 break; 178 179 case top: 180 posA.setTop(posA.getTop() + diff); 181 posA.setHeight(posA.getHeight() - diff); 182 183 posB.setHeight(posB.getHeight() - diff); 184 break; 185 case bottom: 186 posB.setTop(posB.getTop() + diff); 187 posB.setHeight(posB.getHeight() - diff); 188 189 posA.setHeight(posA.getHeight() - diff); 190 break; 191 default: 192 // nothing to do 193 } 194 } 195 } 196 197 /** 198 * Checks whether the two position rectangles collide.<p> 199 * 200 * @param posA the first position to check 201 * @param posB the second position to check 202 * @param margin the required margin 203 * 204 * @return <code>true</code> if the two position rectangles collide 205 */ 206 public static boolean checkCollision(CmsPositionBean posA, CmsPositionBean posB, int margin) { 207 208 // check for non collision is easier 209 if ((posA.getLeft() - margin) >= (posB.getLeft() + posB.getWidth())) { 210 // posA is right of posB 211 return false; 212 } 213 if ((posA.getLeft() + posA.getWidth()) <= (posB.getLeft() - margin)) { 214 // posA is left of posB 215 return false; 216 } 217 if ((posA.getTop() - margin) >= (posB.getTop() + posB.getHeight())) { 218 // posA is bellow posB 219 return false; 220 } 221 if ((posA.getTop() + posA.getHeight()) <= (posB.getTop() - margin)) { 222 // posA is above posB 223 return false; 224 } 225 226 // in any other case the position rectangles collide 227 return true; 228 } 229 230 /** 231 * Collects the position information of the given UI object and returns a position info bean.<p> 232 * 233 * @param element the object to read the position data from 234 * 235 * @return the position data 236 */ 237 public static CmsPositionBean generatePositionInfo(Element element) { 238 239 CmsPositionBean result = new CmsPositionBean(); 240 result.setHeight(element.getOffsetHeight()); 241 result.setWidth(element.getOffsetWidth()); 242 result.setTop(element.getAbsoluteTop()); 243 result.setLeft(element.getAbsoluteLeft()); 244 return result; 245 } 246 247 /** 248 * Collects the position information of the given UI object and returns a position info bean.<p> 249 * 250 * @param uiObject the object to read the position data from 251 * 252 * @return the position data 253 */ 254 public static CmsPositionBean generatePositionInfo(UIObject uiObject) { 255 256 return generatePositionInfo(uiObject.getElement()); 257 } 258 259 /** 260 * Returns the bounding rectangle dimensions of the element including all floated elements.<p> 261 * 262 * @param panel the panel 263 * 264 * @return the position info 265 */ 266 public static CmsPositionBean getBoundingClientRect(Element panel) { 267 268 return getBoundingClientRect(panel, true); 269 270 } 271 272 /** 273 * Returns the bounding rectangle dimensions of the element including all floated elements.<p> 274 * 275 * @param panel the panel 276 * @param addScroll if true, the result will contain the coordinates in the document's coordinate system, not the viewport coordinate system 277 * 278 * @return the position info 279 */ 280 public static CmsPositionBean getBoundingClientRect(Element panel, boolean addScroll) { 281 282 CmsPositionBean result = new CmsPositionBean(); 283 getBoundingClientRect( 284 panel, 285 result, 286 addScroll ? Window.getScrollLeft() : 0, 287 addScroll ? Window.getScrollTop() : 0); 288 return result; 289 } 290 291 /** 292 * Returns a position info representing the dimensions of all visible child elements of the given panel (excluding elements with position:absolute). 293 * If the panel has no visible child elements, it's outer dimensions are returned.<p> 294 * 295 * @param panel the panel 296 * 297 * @return the position info 298 */ 299 public static CmsPositionBean getInnerDimensions(Element panel) { 300 301 boolean first = true; 302 int top = 0; 303 int left = 0; 304 int bottom = 0; 305 int right = 0; 306 Element child = panel.getFirstChildElement(); 307 while (child != null) { 308 String tagName = child.getTagName(); 309 if (tagName.equalsIgnoreCase("br") 310 || tagName.equalsIgnoreCase("tr") 311 || tagName.equalsIgnoreCase("thead") 312 || tagName.equalsIgnoreCase("tfoot") 313 || tagName.equalsIgnoreCase("script") 314 || tagName.equalsIgnoreCase("style")) { 315 // ignore tags with no relevant position info 316 child = child.getNextSiblingElement(); 317 continue; 318 } 319 String positioning = CmsDomUtil.getCurrentStyle(child, Style.position); 320 if (!Display.NONE.getCssName().equals(CmsDomUtil.getCurrentStyle(child, Style.display)) 321 && !(positioning.equalsIgnoreCase(Position.ABSOLUTE.getCssName()) 322 || positioning.equalsIgnoreCase(Position.FIXED.getCssName()))) { 323 CmsPositionBean childDimensions = getBoundingClientRect(child); 324 if (first) { 325 first = false; 326 top = childDimensions.getTop(); 327 left = childDimensions.getLeft(); 328 bottom = top + childDimensions.getHeight(); 329 right = left + childDimensions.getWidth(); 330 } else { 331 int wTop = childDimensions.getTop(); 332 top = top < wTop ? top : wTop; 333 int wLeft = childDimensions.getLeft(); 334 left = left < wLeft ? left : wLeft; 335 int wBottom = wTop + childDimensions.getHeight(); 336 bottom = bottom > wBottom ? bottom : wBottom; 337 int wRight = wLeft + childDimensions.getWidth(); 338 right = right > wRight ? right : wRight; 339 } 340 } 341 child = child.getNextSiblingElement(); 342 } 343 if (!first) { 344 CmsPositionBean result = new CmsPositionBean(); 345 result.setHeight(bottom - top); 346 result.setWidth(right - left); 347 result.setTop(top); 348 result.setLeft(left); 349 return result; 350 } else { 351 return getBoundingClientRect(panel); 352 } 353 } 354 355 /** 356 * Checks whether a value is in a given interval (including the end points).<p> 357 * 358 * @param min the minimum of the interval 359 * @param max the maximum of the interval 360 * @param value the value to check 361 * 362 * @return true if the value is in the given interval 363 */ 364 public static boolean isInRangeInclusive(int min, int max, int value) { 365 366 return (min <= value) && (value <= max); 367 } 368 369 /** 370 * Uses the getBoundingClientRect method to evaluate the element dimensions.<p> 371 * 372 * @param element the element 373 * @param pos the position bean 374 * @param scrollLeft the window scroll position left 375 * @param scrollTop the window scroll position top 376 */ 377 private static native void getBoundingClientRect( 378 Element element, 379 CmsPositionBean pos, 380 int scrollLeft, 381 int scrollTop)/*-{ 382 383 var rect = element.getBoundingClientRect(); 384 pos.@org.opencms.gwt.client.util.CmsPositionBean::m_top=Math.round(rect.top+scrollTop); 385 pos.@org.opencms.gwt.client.util.CmsPositionBean::m_left=Math.round(rect.left+scrollLeft); 386 pos.@org.opencms.gwt.client.util.CmsPositionBean::m_height=Math.round(rect.height); 387 pos.@org.opencms.gwt.client.util.CmsPositionBean::m_width=Math.round(rect.width); 388 }-*/; 389 390 /** 391 * Checks if the rectangle defined by this bean contains the given point.<p> 392 * 393 * @param x the horizontal coordinate 394 * @param y the vertical coordinate 395 * 396 * @return true if this object contains the given point 397 */ 398 public boolean containsPoint(int x, int y) { 399 400 return isInRangeInclusive(getLeft(), (getLeft() + getWidth()) - 1, x) 401 && isInRangeInclusive(getTop(), (getTop() + getHeight()) - 1, y); 402 } 403 404 /** 405 * Increases the dimensions to completely surround the child.<p> 406 * 407 * @param child the child position info 408 * @param padding the padding to apply 409 */ 410 public void ensureSurrounds(CmsPositionBean child, int padding) { 411 412 // increase the size of the outer rectangle 413 if ((getLeft() + padding) > child.getLeft()) { 414 int diff = getLeft() - child.getLeft(); 415 // ensure padding 416 diff += padding; 417 setLeft(getLeft() - diff); 418 setWidth(getWidth() + diff); 419 } 420 if ((getTop() + padding) > child.getTop()) { 421 int diff = getTop() - child.getTop(); 422 diff += padding; 423 setTop(getTop() - diff); 424 setHeight(getHeight() + diff); 425 } 426 if ((getLeft() + getWidth()) < (child.getLeft() + child.getWidth() + padding)) { 427 int diff = (child.getLeft() + child.getWidth()) - (getLeft() + getWidth()); 428 diff += padding; 429 setWidth(getWidth() + diff); 430 } 431 if ((getTop() + getHeight()) < (child.getTop() + child.getHeight() + padding)) { 432 int diff = (child.getTop() + child.getHeight()) - (getTop() + getHeight()); 433 diff += padding; 434 setHeight(getHeight() + diff); 435 } 436 } 437 438 /** 439 * Returns over which area of this the given position is. Will return <code>null</code> if the provided position is not within this position.<p> 440 * 441 * @param absLeft the left position 442 * @param absTop the right position 443 * @param offset the border offset 444 * 445 * @return the area 446 */ 447 public Area getArea(int absLeft, int absTop, int offset) { 448 449 if (isOverElement(absLeft, absTop)) { 450 if (absLeft < (m_left + 10)) { 451 // left border 452 if (absTop < (m_top + offset)) { 453 // top left corner 454 return Area.CORNER_TOP_LEFT; 455 } else if (absTop > ((m_top + m_height) - offset)) { 456 // bottom left corner 457 return Area.CORNER_BOTTOM_LEFT; 458 } 459 return Area.BORDER_LEFT; 460 } 461 if (absLeft > ((m_left + m_width) - offset)) { 462 // right border 463 if (absTop < (m_top + offset)) { 464 // top right corner 465 return Area.CORNER_TOP_RIGHT; 466 // fixing opposite corner 467 } else if (absTop > ((m_top + m_height) - offset)) { 468 // bottom right corner 469 return Area.CORNER_BOTTOM_RIGHT; 470 // fixing opposite corner 471 } 472 return Area.BORDER_RIGHT; 473 } 474 if (absTop < (m_top + offset)) { 475 // border top 476 return Area.BORDER_TOP; 477 } else if (absTop > ((m_top + m_height) - offset)) { 478 // border bottom 479 return Area.BORDER_BOTTOM; 480 } 481 return Area.CENTER; 482 } 483 return null; 484 } 485 486 /** 487 * Returns the height.<p> 488 * 489 * @return the height 490 */ 491 public int getHeight() { 492 493 return m_height; 494 } 495 496 /** 497 * Returns the left.<p> 498 * 499 * @return the left 500 */ 501 public int getLeft() { 502 503 return m_left; 504 } 505 506 /** 507 * Returns the top.<p> 508 * 509 * @return the top 510 */ 511 public int getTop() { 512 513 return m_top; 514 } 515 516 /** 517 * Returns the width.<p> 518 * 519 * @return the width 520 */ 521 public int getWidth() { 522 523 return m_width; 524 } 525 526 /** 527 * Checks whether the given position is completely surrounded by this position.<p> 528 * 529 * @param child the child position 530 * @param padding the padding to use 531 * 532 * @return <code>true</code> if the child position is completely surrounded 533 */ 534 public boolean isInside(CmsPositionBean child, int padding) { 535 536 return ((getLeft() + padding) < child.getLeft()) // checking left border 537 && ((getTop() + padding) < child.getTop()) // checking top border 538 && (((getLeft() + getWidth()) - padding) > (child.getLeft() + child.getWidth())) // checking right border 539 && (((getTop() + getHeight()) - padding) > (child.getTop() + child.getHeight())); // checking bottom border 540 } 541 542 /** 543 * Returns if given position is inside the position beans coordinates.<p> 544 * 545 * @param absLeft the absolute left position 546 * @param absTop the absolute top position 547 * 548 * @return true if the given position if within the beans coordinates 549 */ 550 public boolean isOverElement(int absLeft, int absTop) { 551 552 if ((absTop > m_top) && (absTop < (m_top + m_height)) && (absLeft > m_left) && (absLeft < (m_left + m_width))) { 553 return true; 554 } 555 /* */ 556 return false; 557 } 558 559 /** 560 * Returns if given absolute top is above the vertical middle of the position beans coordinates.<p> 561 * 562 * @param absTop the absolute top position 563 * @return true if given absolute top is above the vertical middle 564 */ 565 public boolean isOverTopHalf(int absTop) { 566 567 if (absTop < (m_top + (m_height / 2))) { 568 return true; 569 } 570 return false; 571 } 572 573 /** 574 * Sets the height.<p> 575 * 576 * @param height the height to set 577 */ 578 public void setHeight(int height) { 579 580 m_height = height; 581 } 582 583 /** 584 * Sets the left.<p> 585 * 586 * @param left the left to set 587 */ 588 public void setLeft(int left) { 589 590 m_left = left; 591 } 592 593 /** 594 * Sets the top.<p> 595 * 596 * @param top the top to set 597 */ 598 public void setTop(int top) { 599 600 m_top = top; 601 } 602 603 /** 604 * Sets the width.<p> 605 * 606 * @param width the width to set 607 */ 608 public void setWidth(int width) { 609 610 m_width = width; 611 } 612 613 /** 614 * @see java.lang.Object#toString() 615 */ 616 @Override 617 public String toString() { 618 619 return "top: " + m_top + " left: " + m_left + " height: " + m_height + " width: " + m_width; 620 } 621 622}