/* * Copyright 2003-2011 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jetbrains.mps.nodeEditor.cellLayout; import jetbrains.mps.editor.runtime.TextBuilderImpl; import jetbrains.mps.editor.runtime.impl.LayoutConstraints; import jetbrains.mps.editor.runtime.style.StyleAttributes; import jetbrains.mps.logging.Logger; import jetbrains.mps.nodeEditor.EditorSettings; import jetbrains.mps.nodeEditor.cells.APICellAdapter; import jetbrains.mps.nodeEditor.cells.GeometryUtil; import jetbrains.mps.openapi.editor.TextBuilder; import jetbrains.mps.openapi.editor.cells.CellTraversalUtil; import jetbrains.mps.openapi.editor.cells.EditorCell; import jetbrains.mps.openapi.editor.cells.EditorCell_Collection; import org.apache.log4j.LogManager; import java.awt.Rectangle; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; /** * User: Sergey Dmitriev * Date: Jan 19, 2005 */ public class CellLayout_Flow extends AbstractCellLayout { private static Logger LOG = Logger.wrap(LogManager.getLogger(CellLayout_Flow.class)); /* wStart <------> |--------|------------------| |--------| blah blah blah | |blah blah blah |----------| |----------------|----------| <---- wEnd------> */ private void init() { myWStart = 0; myWEnd = 0; myRowCount = 1; myMaxDescent = 0; myMaxAscent = 0; myFirstLineHeight = -1; myBossLayout = null; myCurrentLineLayouts = new HashSet<CellLayout_Flow>(); myCurrentLine = new ArrayList<EditorCell>(); } private int myWStart = 0; private int myWEnd = 0; private int myRowCount = 1; private int myMaxDescent = 0; private int myMaxAscent = 0; private int myFirstLineHeight = -1; private CellLayout_Flow myBossLayout = null; private Set<CellLayout_Flow> myCurrentLineLayouts = new HashSet<CellLayout_Flow>(); private java.util.List<EditorCell> myCurrentLine = new ArrayList<EditorCell>(); private void setWStart(int WStart) { myWStart = WStart; } public int getWStart() { return myWStart; } public int getWEnd() { return myWEnd; } private void setBossLayout(CellLayout_Flow bossLayout) { myBossLayout = bossLayout; } private int getMaxX() { return EditorSettings.getInstance().getVerticalBoundWidth(); } @Override public void doLayout(EditorCell_Collection editorCells) { if (myBossLayout == null) init(); new FlowLayouter(editorCells).doLayout(); } private void setMaxDescent(int maxDescent) { this.myMaxDescent = maxDescent; } private void setMaxAscent(int maxAscent) { this.myMaxAscent = maxAscent; } private java.util.List<EditorCell> getCurrentLine() { return myCurrentLine; } private CellLayout_Flow getFlowLayout(EditorCell editorCell) { if (editorCell instanceof EditorCell_Collection) { EditorCell_Collection editorCell_collection = (EditorCell_Collection) editorCell; jetbrains.mps.openapi.editor.cells.CellLayout cellLayout = editorCell_collection.getCellLayout(); if (cellLayout instanceof CellLayout_Flow) { return (CellLayout_Flow) cellLayout; } } return null; } private void setFirstLineHeight(int firstLineHeight) { this.myFirstLineHeight = firstLineHeight; } private Set<CellLayout_Flow> getCurrentLineLayouts() { return myCurrentLineLayouts; } //---------------- // FlowLayouter //---------------- private class FlowLayouter { private int myX; private int myY; private int myMaxRightX; private EditorCell_Collection myEditorCells; private boolean myNextIsPunctuation; private boolean myToSkip; private int myMaxX; public FlowLayouter(EditorCell_Collection editorCells) { this.myEditorCells = editorCells; myX = editorCells.getX() + myWStart; myY = editorCells.getY(); myMaxRightX = myX; if (editorCells.getStyle().isSpecified(StyleAttributes.MAX_WIDTH)) { myMaxX = editorCells.getX() + editorCells.getStyle().get(StyleAttributes.MAX_WIDTH); } else { myMaxX = getMaxX(); } } public void doLayout() { if (myBossLayout == null) { getCurrentLine().clear(); getCurrentLineLayouts().clear(); } for (EditorCell cell : myEditorCells) { if (myToSkip) { myToSkip = false; myNextIsPunctuation = false; continue; } //testing the next cell EditorCell nextCell = CellTraversalUtil.getNextSibling(cell); myNextIsPunctuation = false; if (nextCell != null) { if (APICellAdapter.isPunctuationLayout(nextCell)) { myNextIsPunctuation = true; } } //if no flow if (LayoutConstraints.NOFLOW_LAYOUT_CONSTRAINT.getName().equals(cell.getStyle().get(StyleAttributes.LAYOUT_CONSTRAINT))) { if (!getCurrentLine().isEmpty()) { alignLine(); nextLine(); } cell.moveTo(myEditorCells.getX(), myY); cell.relayout(); myY += cell.getHeight(); myMaxRightX = Math.max(myMaxRightX, cell.getX() + cell.getWidth()); } else //if flow layout if (getFlowLayout(cell) != null) { CellLayout_Flow cellLayout_flow = getFlowLayout(cell); cellLayout_flow.setMaxAscent(myMaxAscent); cellLayout_flow.setMaxDescent(myMaxDescent); cellLayout_flow.setWStart(myX - myEditorCells.getX()); cellLayout_flow.getCurrentLine().clear(); cellLayout_flow.getCurrentLine().addAll(CellLayout_Flow.this.getCurrentLine()); cellLayout_flow.getCurrentLineLayouts().clear(); cellLayout_flow.getCurrentLineLayouts().addAll(CellLayout_Flow.this.getCurrentLineLayouts()); cell.setX(myEditorCells.getX()); cell.setY(myY); cellLayout_flow.setBossLayout(CellLayout_Flow.this); cell.relayout(); myMaxRightX = Math.max(myMaxRightX, cell.getX() + cell.getWidth()); setMaxAscent(Math.max(myMaxAscent, cellLayout_flow.myMaxAscent)); setMaxDescent(Math.max(myMaxDescent, cellLayout_flow.myMaxDescent)); CellLayout_Flow.this.getCurrentLine().clear(); CellLayout_Flow.this.getCurrentLine().addAll(cellLayout_flow.getCurrentLine()); CellLayout_Flow.this.getCurrentLineLayouts().clear(); CellLayout_Flow.this.getCurrentLineLayouts().addAll(cellLayout_flow.getCurrentLineLayouts()); if (cellLayout_flow.myRowCount >= 2) { myX = cell.getX() + cellLayout_flow.myWEnd; myY = cell.getY() + cell.getHeight() - cellLayout_flow.myMaxAscent - cellLayout_flow.myMaxDescent; myRowCount += cellLayout_flow.myRowCount - 1; } else { myX += cell.getWidth(); } } else { //punctuation must be at the same line as previous cell cell.relayout(); int allocatedWidth = cell.getWidth(); if (myNextIsPunctuation) { assert nextCell != null; nextCell.relayout(); allocatedWidth += nextCell.getWidth(); } //if end-of-line if (allocatedWidth + myX >= myMaxX) { alignLine(); nextLine(); addCell(cell); if (myNextIsPunctuation) { addCell(nextCell); myToSkip = true; } } else {//default addCell(cell); } } } boolean currentLineIsEmpty = CellLayout_Flow.this.getCurrentLine().isEmpty(); if (!currentLineIsEmpty) alignLine(); int lastLineHeight = myMaxAscent + myMaxDescent; myEditorCells.setHeight((myY + lastLineHeight) - myEditorCells.getY()); myEditorCells.setWidth(myMaxRightX - myEditorCells.getX()); myWEnd = myX - myEditorCells.getX(); } private void nextLine() { myY += myMaxAscent + myMaxDescent; myX = myEditorCells.getX(); setMaxAscent(0); setMaxDescent(0); getCurrentLine().clear(); getCurrentLineLayouts().clear(); } private void alignLine() { for (EditorCell cell : CellLayout_Flow.this.getCurrentLine()) { cell.setBaseline(myY + myMaxAscent); } for (CellLayout_Flow layout_flow : getCurrentLineLayouts()) { layout_flow.setMaxAscent(myMaxAscent); layout_flow.setMaxDescent(myMaxDescent); if (layout_flow.myFirstLineHeight == -1) layout_flow.setFirstLineHeight(myMaxAscent + myMaxDescent); } if (myFirstLineHeight == -1) myFirstLineHeight = myMaxAscent + myMaxDescent; myMaxRightX = Math.max(myX, myMaxRightX); myRowCount++; } private void addCell(EditorCell cell) { cell.setX(myX); cell.relayout(); myX += cell.getWidth(); if (myNextIsPunctuation) { myX -= cell.getRightInset(); } setMaxAscent(Math.max(myMaxAscent, cell.getAscent())); setMaxDescent(Math.max(myMaxDescent, cell.getDescent())); CellLayout_Flow.this.getCurrentLine().add(cell); } } //--FlowLayouter @Override public List<? extends EditorCell> getSelectionCells(EditorCell_Collection editorCells) { LOG.assertLog(getFlowLayout(editorCells) == this, "Assertion failed."); List<EditorCell> result = new ArrayList<EditorCell>(); for (EditorCell cell : editorCells) { result.add(cell); } return result; } @Override public List<Rectangle> getSelectionBounds(EditorCell_Collection editorCells) { LOG.assertLog(getFlowLayout(editorCells) == this, "Assertion failed."); List<Rectangle> result = new ArrayList<Rectangle>(); for (EditorCell cell : editorCells) { result.add(GeometryUtil.getBounds(cell)); } return result; } @Override public TextBuilder doLayoutText(Iterable<EditorCell> editorCells) { TextBuilder result = new TextBuilderImpl(); Iterator<EditorCell> it = editorCells.iterator(); while (it.hasNext()) { result.appendToTheBottom(doLayoutRow(it)); } return result; } private TextBuilder doLayoutRow(Iterator<EditorCell> it) { TextBuilder result = new TextBuilderImpl(); for (; it.hasNext(); ) { EditorCell editorCell = it.next(); if (LayoutConstraints.NOFLOW_LAYOUT_CONSTRAINT.getName().equals(editorCell.getStyle().get(StyleAttributes.LAYOUT_CONSTRAINT))) { return result.appendToTheBottom(editorCell.renderText()); } result.appendToTheRight(editorCell.renderText(), !APICellAdapter.isPunctuationLayout(editorCell)); } return result; } public String toString() { return "Flow"; } }