001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (C) Alkacon Software (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.acacia.client.ui; 029 030import org.opencms.gwt.client.util.CmsClientStringUtil; 031import org.opencms.gwt.client.util.CmsDomUtil; 032import org.opencms.gwt.client.util.CmsPositionBean; 033 034import java.util.ArrayList; 035import java.util.HashMap; 036import java.util.List; 037import java.util.Map; 038 039import com.google.gwt.core.client.GWT; 040import com.google.gwt.dom.client.Element; 041import com.google.gwt.dom.client.Style; 042import com.google.gwt.dom.client.Style.Display; 043import com.google.gwt.dom.client.Style.Unit; 044import com.google.gwt.event.dom.client.ClickEvent; 045import com.google.gwt.event.dom.client.ClickHandler; 046import com.google.gwt.event.dom.client.HasClickHandlers; 047import com.google.gwt.event.shared.HandlerRegistration; 048import com.google.gwt.uibinder.client.UiBinder; 049import com.google.gwt.uibinder.client.UiField; 050import com.google.gwt.user.client.Window; 051import com.google.gwt.user.client.ui.Composite; 052import com.google.gwt.user.client.ui.FlowPanel; 053import com.google.gwt.user.client.ui.HTMLPanel; 054import com.google.gwt.user.client.ui.RootPanel; 055import com.google.gwt.user.client.ui.Widget; 056 057/** 058 * In-line edit overlay covering rest of the page.<p> 059 */ 060public class CmsInlineEditOverlay extends Composite implements HasClickHandlers { 061 062 /** The ui binder. */ 063 interface I_CmsInlineEditOverlayUiBinder extends UiBinder<HTMLPanel, CmsInlineEditOverlay> { 064 // nothing to do 065 } 066 067 /** The width rquired by the button bar. */ 068 private static final int BUTTON_BAR_WIDTH = 28; 069 070 /** List of present overlays. */ 071 private static List<CmsInlineEditOverlay> m_overlays = new ArrayList<CmsInlineEditOverlay>(); 072 073 /** The ui binder instance. */ 074 private static I_CmsInlineEditOverlayUiBinder uiBinder = GWT.create(I_CmsInlineEditOverlayUiBinder.class); 075 076 /** Bottom border. */ 077 @UiField 078 protected Element m_borderBottom; 079 080 /** Left border. */ 081 @UiField 082 protected Element m_borderLeft; 083 084 /** Right border. */ 085 @UiField 086 protected Element m_borderRight; 087 088 /** Top border. */ 089 @UiField 090 protected Element m_borderTop; 091 092 /** The button bar element. */ 093 @UiField 094 protected Element m_buttonBar; 095 096 /** Edit overlay. */ 097 @UiField 098 protected Element m_overlayBottom; 099 100 /** Edit overlay. */ 101 @UiField 102 protected Element m_overlayLeft; 103 104 /** Edit overlay. */ 105 @UiField 106 protected Element m_overlayRight; 107 108 /** Edit overlay. */ 109 @UiField 110 protected Element m_overlayTop; 111 112 /** The edit button panel. */ 113 @UiField 114 FlowPanel m_buttonPanel; 115 116 /** Style of border. */ 117 private Style m_borderBottomStyle; 118 119 /** Style of border. */ 120 private Style m_borderLeftStyle; 121 122 /** Style of border. */ 123 private Style m_borderRightStyle; 124 125 /** Style of border. */ 126 private Style m_borderTopStyle; 127 128 /** Map of attached edit buttons and their absolute top positions. */ 129 private Map<CmsInlineEntityWidget, Integer> m_buttons; 130 131 /** The current overlay position. */ 132 private CmsPositionBean m_currentPosition; 133 134 /** The element to surround with the overlay. */ 135 private Element m_element; 136 137 /** Flag indicating this overlay has a button bar. */ 138 private boolean m_hasButtonBar; 139 140 /** The main panel. */ 141 private HTMLPanel m_main; 142 143 /** The overlay offset. */ 144 private int m_offset = 3; 145 146 /** Style of overlay. */ 147 private Style m_overlayBottomStyle; 148 149 /** Style of overlay. */ 150 private Style m_overlayLeftStyle; 151 152 /** Style of overlay. */ 153 private Style m_overlayRightStyle; 154 155 /** Style of overlay. */ 156 private Style m_overlayTopStyle; 157 158 /** 159 * Constructor.<p> 160 * 161 * @param element the element to surround with the overlay 162 */ 163 public CmsInlineEditOverlay(Element element) { 164 165 m_main = uiBinder.createAndBindUi(this); 166 initWidget(m_main); 167 m_element = element; 168 m_overlayLeftStyle = m_overlayLeft.getStyle(); 169 m_overlayBottomStyle = m_overlayBottom.getStyle(); 170 m_overlayRightStyle = m_overlayRight.getStyle(); 171 m_overlayTopStyle = m_overlayTop.getStyle(); 172 m_borderBottomStyle = m_borderBottom.getStyle(); 173 m_borderLeftStyle = m_borderLeft.getStyle(); 174 m_borderRightStyle = m_borderRight.getStyle(); 175 m_borderTopStyle = m_borderTop.getStyle(); 176 m_buttonBar.getStyle().setDisplay(Display.NONE); 177 m_buttonPanel.addDomHandler(new ClickHandler() { 178 179 public void onClick(ClickEvent event) { 180 181 // prevent the click event to propagated from the button panel to the main widget 182 event.stopPropagation(); 183 } 184 }, ClickEvent.getType()); 185 m_buttons = new HashMap<CmsInlineEntityWidget, Integer>(); 186 } 187 188 /** 189 * Adds an overlay surrounding the given DOM element.<p> 190 * 191 * @param element the element 192 * 193 * @return the overlay widget 194 */ 195 public static CmsInlineEditOverlay addOverlayForElement(Element element) { 196 197 CmsInlineEditOverlay overlay = new CmsInlineEditOverlay(element); 198 if (!m_overlays.isEmpty()) { 199 m_overlays.get(m_overlays.size() - 1).setVisible(false); 200 } 201 m_overlays.add(overlay); 202 RootPanel.get().add(overlay); 203 overlay.updatePosition(); 204 overlay.checkZIndex(); 205 return overlay; 206 } 207 208 /** 209 * Returns the root overlay if available.<p> 210 * 211 * @return the root overlay 212 */ 213 public static CmsInlineEditOverlay getRootOverlay() { 214 215 return m_overlays.isEmpty() ? null : m_overlays.get(0); 216 } 217 218 /** 219 * Removes all present overlays.<p> 220 */ 221 public static void removeAll() { 222 223 for (CmsInlineEditOverlay overlay : m_overlays) { 224 overlay.removeFromParent(); 225 } 226 m_overlays.clear(); 227 } 228 229 /** 230 * Removes the last overlay to display the previous or none.<p> 231 */ 232 public static void removeLastOverlay() { 233 234 if (!m_overlays.isEmpty()) { 235 CmsInlineEditOverlay last = m_overlays.remove(m_overlays.size() - 1); 236 last.removeFromParent(); 237 } 238 if (!m_overlays.isEmpty()) { 239 m_overlays.get(m_overlays.size() - 1).setVisible(true); 240 } 241 } 242 243 /** 244 * Updates the current overlay's position.<p> 245 */ 246 public static void updateCurrentOverlayPosition() { 247 248 if (!m_overlays.isEmpty()) { 249 m_overlays.get(m_overlays.size() - 1).updatePosition(); 250 } 251 } 252 253 /** 254 * Adds a button widget to the button panel.<p> 255 * 256 * @param widget the button widget 257 * @param absoluteTop the absolute top position 258 */ 259 public void addButton(CmsInlineEntityWidget widget, int absoluteTop) { 260 261 setButtonBarVisible(true); 262 m_buttonPanel.add(widget); 263 setButtonPosition(widget, absoluteTop); 264 } 265 266 /** 267 * @see com.google.gwt.event.dom.client.HasClickHandlers#addClickHandler(com.google.gwt.event.dom.client.ClickHandler) 268 */ 269 public HandlerRegistration addClickHandler(ClickHandler handler) { 270 271 return addDomHandler(handler, ClickEvent.getType()); 272 } 273 274 /** 275 * Increases the overlay z-index if necessary.<p> 276 */ 277 public void checkZIndex() { 278 279 int zIndex = 100000; 280 Element parent = m_element.getParentElement(); 281 while (parent != null) { 282 int parentIndex = CmsDomUtil.getCurrentStyleInt(parent, CmsDomUtil.Style.zIndex); 283 if (parentIndex > zIndex) { 284 zIndex = parentIndex; 285 } 286 parent = parent.getParentElement(); 287 } 288 if (zIndex > 100000) { 289 getElement().getStyle().setZIndex(zIndex); 290 } 291 } 292 293 /** 294 * Clears and hides the button panel.<p> 295 */ 296 public void clearButtonPanel() { 297 298 m_buttonPanel.clear(); 299 m_buttons.clear(); 300 setButtonBarVisible(false); 301 } 302 303 /** 304 * Updates the position of the given button widget.<p> 305 * 306 * @param widget the button widget 307 * @param absoluteTop the top absolute top position 308 */ 309 public void setButtonPosition(CmsInlineEntityWidget widget, int absoluteTop) { 310 311 if (m_buttonPanel.getWidgetIndex(widget) > -1) { 312 int buttonBarTop = CmsClientStringUtil.parseInt(m_buttonBar.getStyle().getTop()); 313 if (absoluteTop < buttonBarTop) { 314 absoluteTop = buttonBarTop; 315 } 316 int positionTop = getAvailablePosition(widget, absoluteTop) - buttonBarTop; 317 widget.getElement().getStyle().setTop(positionTop, Unit.PX); 318 if (CmsClientStringUtil.parseInt(m_buttonBar.getStyle().getHeight()) < (positionTop + 20)) { 319 increaseOverlayHeight(positionTop + 20); 320 } 321 } 322 } 323 324 /** 325 * Sets the overlay offset.<p> 326 * 327 * @param offset the offset 328 */ 329 public void setOffset(int offset) { 330 331 m_offset = offset; 332 } 333 334 /** 335 * @see com.google.gwt.user.client.ui.UIObject#setVisible(boolean) 336 */ 337 @Override 338 public void setVisible(boolean visible) { 339 340 super.setVisible(visible); 341 if (!visible && m_hasButtonBar) { 342 for (Widget widget : m_buttonPanel) { 343 if (widget instanceof CmsInlineEntityWidget) { 344 ((CmsInlineEntityWidget)widget).setContentHighlightingVisible(false); 345 } 346 } 347 } 348 } 349 350 /** 351 * Updates the overlay position.<p> 352 */ 353 public void updatePosition() { 354 355 setPosition(CmsPositionBean.getBoundingClientRect(m_element)); 356 for (Widget widget : m_buttonPanel) { 357 if (widget instanceof CmsInlineEntityWidget) { 358 ((CmsInlineEntityWidget)widget).positionWidget(); 359 } 360 } 361 } 362 363 /** 364 * Returns the available absolute top position for the given button.<p> 365 * 366 * @param widget the button widget 367 * @param absoluteTop the proposed position 368 * 369 * @return the available position 370 */ 371 private int getAvailablePosition(CmsInlineEntityWidget widget, int absoluteTop) { 372 373 m_buttons.remove(widget); 374 boolean positionBlocked = true; 375 while (positionBlocked) { 376 positionBlocked = false; 377 for (int pos : m_buttons.values()) { 378 if (((pos - 24) < absoluteTop) && (absoluteTop < (pos + 24))) { 379 positionBlocked = true; 380 absoluteTop = pos + 25; 381 break; 382 } 383 } 384 } 385 m_buttons.put(widget, Integer.valueOf(absoluteTop)); 386 return absoluteTop; 387 } 388 389 /** 390 * Increases the overlay height to make space for edit buttons.<p> 391 * 392 * @param height the height to set 393 */ 394 private void increaseOverlayHeight(int height) { 395 396 if (m_currentPosition != null) { 397 m_currentPosition.setHeight(height); 398 setPosition(m_currentPosition); 399 } 400 } 401 402 /** 403 * Sets button bar visibility.<p> 404 * 405 * @param visible <code>true</code> to set the button bar visible 406 */ 407 private void setButtonBarVisible(boolean visible) { 408 409 if (m_hasButtonBar != visible) { 410 m_hasButtonBar = visible; 411 if (m_hasButtonBar) { 412 413 m_buttonBar.getStyle().clearDisplay(); 414 int width = CmsClientStringUtil.parseInt(m_borderTopStyle.getWidth()) + BUTTON_BAR_WIDTH; 415 m_borderTopStyle.setWidth(width, Unit.PX); 416 m_borderBottomStyle.setWidth(width, Unit.PX); 417 m_borderRightStyle.setLeft( 418 CmsClientStringUtil.parseInt(m_borderRightStyle.getLeft()) + BUTTON_BAR_WIDTH, 419 Unit.PX); 420 } else { 421 m_buttonBar.getStyle().setDisplay(Display.NONE); 422 int width = CmsClientStringUtil.parseInt(m_borderTopStyle.getWidth()) - BUTTON_BAR_WIDTH; 423 m_borderTopStyle.setWidth(width, Unit.PX); 424 m_borderBottomStyle.setWidth(width, Unit.PX); 425 m_borderRightStyle.setLeft( 426 CmsClientStringUtil.parseInt(m_borderRightStyle.getLeft()) - BUTTON_BAR_WIDTH, 427 Unit.PX); 428 } 429 } 430 } 431 432 /** 433 * Sets position and size of the overlay area.<p> 434 * 435 * @param position the position of highlighted area 436 */ 437 private void setPosition(CmsPositionBean position) { 438 439 m_currentPosition = position; 440 setSelectPosition(position.getLeft(), position.getTop(), position.getHeight(), position.getWidth()); 441 } 442 443 /** 444 * Sets position and size of the overlay area.<p> 445 * 446 * @param posX the new X position 447 * @param posY the new Y position 448 * @param height the new height 449 * @param width the new width 450 */ 451 private void setSelectPosition(int posX, int posY, int height, int width) { 452 453 int useWidth = Window.getClientWidth(); 454 int bodyWidth = RootPanel.getBodyElement().getClientWidth() + RootPanel.getBodyElement().getOffsetLeft(); 455 if (bodyWidth > useWidth) { 456 useWidth = bodyWidth; 457 } 458 int useHeight = Window.getClientHeight(); 459 int bodyHeight = RootPanel.getBodyElement().getClientHeight() + RootPanel.getBodyElement().getOffsetTop(); 460 if (bodyHeight > useHeight) { 461 useHeight = bodyHeight; 462 } 463 464 m_overlayLeftStyle.setWidth(posX - m_offset, Unit.PX); 465 m_overlayLeftStyle.setHeight(useHeight, Unit.PX); 466 467 m_borderLeftStyle.setHeight(height + (4 * m_offset), Unit.PX); 468 m_borderLeftStyle.setTop(posY - (2 * m_offset), Unit.PX); 469 m_borderLeftStyle.setLeft(posX - (2 * m_offset), Unit.PX); 470 471 m_overlayTopStyle.setLeft(posX - m_offset, Unit.PX); 472 m_overlayTopStyle.setWidth(width + (2 * m_offset), Unit.PX); 473 m_overlayTopStyle.setHeight(posY - m_offset, Unit.PX); 474 475 m_borderTopStyle.setLeft(posX - m_offset, Unit.PX); 476 m_borderTopStyle.setTop(posY - (2 * m_offset), Unit.PX); 477 if (m_hasButtonBar) { 478 m_borderTopStyle.setWidth(width + (2 * m_offset) + BUTTON_BAR_WIDTH, Unit.PX); 479 } else { 480 m_borderTopStyle.setWidth(width + (2 * m_offset), Unit.PX); 481 } 482 483 m_overlayBottomStyle.setLeft(posX - m_offset, Unit.PX); 484 m_overlayBottomStyle.setWidth(width + m_offset + m_offset, Unit.PX); 485 m_overlayBottomStyle.setHeight(useHeight - posY - height - m_offset, Unit.PX); 486 m_overlayBottomStyle.setTop(posY + height + m_offset, Unit.PX); 487 488 m_borderBottomStyle.setLeft(posX - m_offset, Unit.PX); 489 m_borderBottomStyle.setTop((posY + height) + m_offset, Unit.PX); 490 if (m_hasButtonBar) { 491 m_borderBottomStyle.setWidth(width + (2 * m_offset) + BUTTON_BAR_WIDTH, Unit.PX); 492 } else { 493 m_borderBottomStyle.setWidth(width + (2 * m_offset), Unit.PX); 494 } 495 496 m_overlayRightStyle.setLeft(posX + width + m_offset, Unit.PX); 497 m_overlayRightStyle.setWidth(useWidth - posX - width - m_offset, Unit.PX); 498 m_overlayRightStyle.setHeight(useHeight, Unit.PX); 499 500 m_borderRightStyle.setHeight(height + (4 * m_offset), Unit.PX); 501 m_borderRightStyle.setTop(posY - (2 * m_offset), Unit.PX); 502 if (m_hasButtonBar) { 503 m_borderRightStyle.setLeft(posX + width + m_offset + BUTTON_BAR_WIDTH, Unit.PX); 504 } else { 505 m_borderRightStyle.setLeft(posX + width + m_offset, Unit.PX); 506 } 507 508 m_buttonBar.getStyle().setTop(posY - m_offset, Unit.PX); 509 m_buttonBar.getStyle().setHeight(height + (2 * m_offset), Unit.PX); 510 m_buttonBar.getStyle().setLeft(posX + width + m_offset + 1, Unit.PX); 511 } 512}