/* GeoGebra - Dynamic Mathematics for Everyone http://www.geogebra.org This file is part of GeoGebra. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. */ /* * DrawPoint.java * * Created on 11. Oktober 2001, 23:59 */ package org.geogebra.common.euclidian.draw; import java.util.ArrayList; import java.util.List; import org.geogebra.common.awt.GColor; import org.geogebra.common.awt.GDimension; import org.geogebra.common.awt.GFont; import org.geogebra.common.awt.GGraphics2D; import org.geogebra.common.awt.GPoint; import org.geogebra.common.awt.GRectangle; import org.geogebra.common.awt.font.GTextLayout; import org.geogebra.common.euclidian.BoundingBox; import org.geogebra.common.euclidian.EuclidianStatic; import org.geogebra.common.euclidian.EuclidianView; import org.geogebra.common.factories.AwtFactory; import org.geogebra.common.gui.util.DropDownList; import org.geogebra.common.gui.util.DropDownList.DropDownListener; import org.geogebra.common.kernel.StringTemplate; import org.geogebra.common.kernel.arithmetic.FunctionalNVar; import org.geogebra.common.kernel.geos.GeoElement; import org.geogebra.common.kernel.geos.GeoList; import org.geogebra.common.kernel.geos.ScreenLocation; import org.geogebra.common.main.App; import org.geogebra.common.util.lang.Unicode; import com.google.j2objc.annotations.WeakOuter; /** * Draw a GeoList containing drawable objects * * @author Markus Hohenwarter */ public final class DrawDropDownList extends CanvasDrawable implements DropDownListener { private static final int LABEL_COMBO_GAP = 10; private static final int COMBO_TEXT_MARGIN = 5; /** coresponding list as geo */ GeoList geoList; /** whether this is visible */ boolean isVisible; /** dropdown */ DropDownList dropDown = null; /** selcted text */ String selectedText; private int selectedHeight; private GRectangle ctrlRect; private GDimension selectedDimension; private boolean latexLabel; private DrawOptions drawOptions; private boolean seLatex; private enum ScrollMode { UP, DOWN, NONE } @WeakOuter private class DrawOptions { private static final int MARGIN = 5; private static final int ROUND = 8; final EuclidianView viewOpt; private int viewHeight = 0; private int viewWidth = 0; // private static final int MAX_COLS_NO_FONT_CHANGE = 5; private class OptionItem { public int index; public int width; public int height; public String text; public boolean latex; GRectangle rect; public OptionItem(GGraphics2D g2, int idx) { index = idx; GeoElement geoItem = geoList.get(idx); if (needsLatex(geoItem)) { text = geoItem.toLaTeXString(false, StringTemplate.latexTemplate); latex = true; } else { text = geoItem .toValueString(StringTemplate.defaultTemplate); latex = isLatexString(text); } if (!"".equals(text)) { if (latex) { GDimension d = measureLatex(g2, geoList, itemFont, text); width = d.getWidth(); height = d.getHeight(); } else { GTextLayout layout = getLayout(g2, text, itemFont); width = (int) Math.round(layout.getBounds().getWidth()); height = (int) Math .round(layout.getBounds().getHeight()); } } rect = null; } public GRectangle getRect() { return rect; } /** * Two OptionItems are equal iff they indexes are the same. * * @param item * to compare. * @return if equal. */ public boolean isEqual(OptionItem item) { if (item == null) { return false; } return index == item.index; } public boolean isHit(int x, int y) { return rect != null && rect.contains(x, y); } public void setRect(GRectangle rect) { this.rect = rect; } } private class DraggedItem { public GPoint startPoint; public OptionItem item; public DraggedItem(int x, int y) { item = getItemAt(x, y); startPoint = new GPoint(x, y); } public boolean isValid() { return item != null; } } private static final int MIN_FONT_SIZE = 12; private int colCount = 1; private int rowCount = 1; private GRectangle rectTable; private GDimension dimItem; private GDimension dimTable; private int left; private int top; private int xPadding; private int yPadding; GFont itemFont; private List<OptionItem> items; private OptionItem hovered; private GColor hoverColor; private GGraphics2D g2; private boolean visible; private int selectedIndex; private int itemFontSize; // startIdx and endIdx defines the range of items that are visible. private int startIdx = -1; private int endIdx; private GRectangle rectUp; private GRectangle rectDown; private boolean scrollNeeded = false; private ScrollMode scrollMode = ScrollMode.NONE; private DraggedItem dragged = null; private int dragOffset; private boolean dragging = false; private boolean dragDirection; public DrawOptions(EuclidianView view) { this.viewOpt = view; items = new ArrayList<DrawDropDownList.DrawOptions.OptionItem>(); hovered = null; hoverColor = GColor.LIGHT_GRAY; } private int getStartIdx() { return getColCount() == 1 ? startIdx : 0; } private int getEndIdx() { return getColCount() == 1 ? endIdx : items.size(); } private boolean isScrollNeeded() { return scrollNeeded; } public void draw(GGraphics2D graphics2, int leftPos, int topPos) { if (!isVisible()) { return; } if (startIdx == -1 && !"".equals(selectedText)) { startIdx = isScrollNeeded() ? geoList.getSelectedIndex() : 0; } this.left = leftPos; this.top = topPos; this.g2 = graphics2; getMetrics(); drawBox(); drawItems(); if (isScrollNeeded()) { drawControls(); } } private void drawItems() { int idx = getStartIdx(); int startRow = 0; int visibleRows = rowCount; if (isScrollNeeded() && idx >= 0 && dragOffset != 0) { idx--; startRow = -1; visibleRows++; } for (int col = 0; col < getColCount(); col++) { for (int row = startRow; row < visibleRows; row++) { if (idx >= 0 && idx < items.size()) { drawItem(col, row, items.get(idx)); } idx++; } } } private void drawItem(int col, int row, OptionItem item) { int rectLeft = left + dimItem.getWidth() * col; int rectTop = top + dimItem.getHeight() * row; if (isScrollNeeded()) { rectTop += rectUp.getHeight(); if (dragDirection) { rectTop -= dragOffset; } else { rectTop += dragOffset; } } if (item.getRect() == null || item.getRect().getX() != rectLeft || item.getRect().getY() != rectTop) { item.setRect(AwtFactory.getPrototype().newRectangle(rectLeft, rectTop, dimItem.getWidth(), dimItem.getHeight())); } drawItem(item, item.isEqual(hovered)); } private void drawItem(OptionItem item, boolean hover) { if (item.rect == null) { return; } int rectLeft = (int) item.rect.getBounds().getX(); int rectTop = (int) item.rect.getBounds().getY(); int rectBottom = rectTop + (int) item.rect.getBounds().getHeight(); boolean clip = false; if (this.isScrollNeeded()) { int ctrlUpY = (int) (rectUp.getBounds().getY() + rectUp.getBounds().getHeight()); int ctrlDownBottom = (int) (rectDown.getBounds().getY() + rectDown.getBounds().getHeight()); // no extra item drawing. if ((item.index == startIdx - 1 && rectBottom < ctrlUpY) || (ctrlDownBottom < rectBottom)) { return; } clip = dragOffset != 0 && ((item.rect.intersects(rectUp) || item.rect.intersects(rectDown))); if (clip) { // Log.debug("CLIPPING"); g2.setClip(rectLeft, ctrlUpY, (int) item.rect.getWidth(), (int) (rectDown.getY() - ctrlUpY)); } } int itemHeight = dimItem.getHeight(); if (hover) { g2.setColor(hoverColor); g2.fillRoundRect(rectLeft, rectTop, dimItem.getWidth(), itemHeight, ROUND, ROUND); } else { g2.setColor(geoList.getBackgroundColor()); g2.fillRect(rectLeft, rectTop, dimItem.getWidth(), itemHeight); } if (item.getRect() == null) { item.setRect(AwtFactory.getPrototype().newRectangle(rectLeft, rectTop, dimItem.getWidth(), itemHeight)); } g2.setPaint(getGeoElement().getObjectColor()); if (item.latex) { GRectangle rect = item.rect.getBounds(); int x = (int) rect.getX(); int y = (int) rect.getY(); drawLatex(g2, geoList, itemFont, item.text, x + (int) ((rect.getWidth() - item.width) / 2), y + (int) ((rect.getHeight() - item.height) / 2)); } else { if (g2.getFont().getSize() != itemFontSize) { g2.setFont(itemFont.deriveFont(GFont.PLAIN, itemFontSize)); } int x = (dimItem.getWidth() - item.width) / 2; int y = (itemHeight - yPadding); EuclidianStatic.drawIndexedString(viewOpt.getApplication(), g2, item.text, rectLeft + x, rectTop + y, false); } if (clip) { g2.resetClip(); } } private void drawBox() { g2.setPaint(geoList.getBackgroundColor()); g2.fillRoundRect(left - 1, top - 1, dimTable.getWidth() + 2, dimTable.getHeight() + 2, ROUND, ROUND); g2.setPaint(GColor.LIGHT_GRAY); g2.drawRoundRect(left - 1, top - 1, dimTable.getWidth() + 2, dimTable.getHeight() + 2, ROUND, ROUND); } public boolean isHit(int x, int y) { return isVisible() && ((rectTable != null && rectTable.contains(x, y)) || (isScrollNeeded() && isControlHit(x, y))); } private boolean handleUpControl(int x, int y) { if (isScrollNeeded() && rectUp != null && rectUp.contains(x, y)) { scrollMode = ScrollMode.UP; dropDown.startScrollTimer(x, y); scrollUp(); return true; } return false; } private boolean handleDownControl(int x, int y) { if (isScrollNeeded() && rectDown != null && rectDown.contains(x, y)) { scrollMode = ScrollMode.DOWN; dropDown.startScrollTimer(x, y); scrollDown(); return true; } return false; } public boolean isControlHit(int x, int y) { return isScrollNeeded() && ((rectUp != null && rectUp.contains(x, y)) || (rectDown != null && rectDown.contains(x, y))); } private void setHovered(OptionItem item) { if (item == null) { return; } if (item.isEqual(hovered)) { return; } drawHovered(false); hovered = item; drawHovered(true); viewOpt.repaintView(); } private void drawHovered(boolean on) { if (hovered != null && hovered.index > -1 && hovered.index < items.size()) { OptionItem item = items.get(hovered.index); drawItem(item, on); } } void scrollUp() { cancelDrag(); scrollBy(-1); } void scrollDown() { cancelDrag(); scrollBy(1); } void scrollBy(int diff) { if (!isScrollNeeded()) { return; } if (startIdx + diff >= 0 && endIdx + diff < items.size() + 1) { startIdx += diff; selectedIndex += diff; update(); // Log.error("repaint 1"); getView().repaintView(); } } public void scroll() { if (!isScrollNeeded()) { return; } switch (scrollMode) { case UP: scrollUp(); break; case DOWN: scrollDown(); break; case NONE: break; default: break; } } public boolean onMouseDown(int x, int y) { if (!visible) { return false; } if (handleUpControl(x, y) || handleDownControl(x, y)) { return true; } if (isScrollNeeded() && !isDragging()) { dropDown.startClickTimer(x, y); } else { return onClick(x, y); } return true; } private boolean isDragging() { if (!isScrollNeeded()) { return false; } return dragging; } public boolean onClick(int x, int y) { OptionItem item = getItemAt(x, y); if (item == null) { return false; } selectedIndex = item.index; selectCurrentItem(); setDragging(false); setVisible(false); return true; } public boolean onDrag(int x, int y) { if (!isScrollNeeded()) { return false; } DraggedItem di = new DraggedItem(x, y); if (di.isValid()) { if (dragged == null || !dragged.isValid()) { dragged = di; return true; } int d = dragged.startPoint.getY() - di.startPoint.getY(); dragDirection = d > 0; int dY = Math.abs(d); int itemHeight = (int) (di.item.getRect().getHeight()); if (dY > 0) { setDragging(true); } int itemDiffs = dY / itemHeight; if (itemDiffs != 0) { dragOffset = dY % itemHeight; if (dragDirection) { scrollBy(itemDiffs); } else { scrollBy(-itemDiffs); } dragged = di; } else { if (getStartIdx() > 0 && getEndIdx() < geoList.size()) { dragOffset = dY; // Log.error("repaint 2"); viewOpt.repaintView(); } } } return true; } private void setDragging(boolean value) { if (!isScrollNeeded()) { return; } dragging = value; if (dragging) { dropDown.stopClickTimer(); } else { dragged = null; } } public void onMouseOver(int x, int y) { if (!isVisible) {// || isControlHit(x, y)) { return; } if (!isHit(x, y)) { if (isScrollNeeded()) { stopScrolling(); setDragging(false); } return; } if (isDragging()) { return; } OptionItem item = getItemAt(x, y); setHovered(item); // if (selectedIndex != item.index) { // selectedIndex = item.index; // viewOpt.repaintView(); // // } } OptionItem getItemAt(int x, int y) { for (OptionItem item : items) { if (item.isHit(x, y)) { return item; } } return null; } // private boolean hoverIntersectControls() { // return isScrollNeeded() && itemHovered.rect != null // && (itemHovered.rect.intersects(rectUp) // || itemHovered.rect.intersects(rectDown)); // } private boolean prepareTable() { itemFont = getLabelFont().deriveFont(GFont.PLAIN, itemFontSize); createItems(); return getTableScale(); } private void getMetrics() { xPadding = 10; yPadding = 10; itemFontSize = getLabelFontSize(); if (!getScrollSettings()) { boolean finished = false; while (!finished && itemFontSize > MIN_FONT_SIZE) { finished = prepareTable(); itemFontSize--; } if (dimItem == null) { prepareTable(); } } int tableWidth = getColCount() * dimItem.getWidth(); int tableHeight = rowCount * dimItem.getHeight(); // Log.debug("[!!] 1 tableHeight: " + tableHeight + " rowCount: " // + rowCount); if (isScrollNeeded() && (top + tableHeight + MARGIN <= viewHeight)) { tableHeight += dimItem.getHeight(); // if (tableHeight + rectDown.getHeight() >= viewHeight) { // tableHeight = (int) (viewHeight - top - MARGIN // - 2 * rectDown.getHeight()); // } } if (top + tableHeight + MARGIN >= viewOpt.getHeight()) { top = (viewOpt.getHeight() - tableHeight - MARGIN); if (top < MARGIN) { top = MARGIN; if (!isScrollNeeded()) { tableHeight -= MARGIN; } } if (isScrollNeeded()) { int h = viewOpt.getHeight(); tableHeight += 2 * (int) rectDown.getHeight(); if (tableHeight > h) { tableHeight = h - 2 * MARGIN; } else { top = (h - tableHeight) / 2; } } } dimTable = AwtFactory.getPrototype().newDimension(tableWidth, tableHeight); if (left + dimTable.getWidth() > viewOpt.getWidth()) { left = (viewOpt.getWidth() - dimTable.getWidth()); } rectTable = AwtFactory.getPrototype().newRectangle(left, top + MARGIN, dimTable.getWidth(), dimTable.getHeight()); if (isScrollNeeded()) { rectUp.setBounds(left, top, (int) (rectUp.getWidth()), (int) (rectUp.getHeight())); rectDown.setBounds(left, top + dimTable.getHeight() - (int) (rectDown.getHeight()), (int) (rectDown.getWidth()), (int) (rectDown.getHeight())); // Log.debug("top: " + top + " tableHeight: " + tableHeight // + "viewHeight: " + viewOpt.getHeight()); // Log.debug("bottom: " // + (top + tableHeight + 2 * rectDown.getHeight()) // + "viewHeight: " + viewOpt.getHeight()); // Log.debug("dimTable: " + dimTable.getHeight() + " down: " // + (top + dimTable.getHeight() + rectDown.getHeight())); } } private void drawControls() { if (!isScrollNeeded()) { return; } // g2.setPaint(GColor.YELLOW); int x = (int) rectUp.getX(); int y = (int) rectUp.getY(); int h = (int) rectUp.getHeight(); int w = (int) rectUp.getWidth(); if (y < MARGIN) { y = MARGIN; } // Log.debug(SCROLL_PFX + " drawing up control at (" + x + ", " + y // + ") w: " + w + " h: " + h); g2.setPaint(geoList.getBackgroundColor()); g2.fillRoundRect(x, y, w, h, ROUND, ROUND); dropDown.drawScrollUp(g2, x, y, w, h, geoList.getBackgroundColor(), false); int x2 = (int) rectDown.getX(); int y2 = (int) rectDown.getY(); int h2 = (int) rectDown.getHeight(); int w2 = (int) rectDown.getWidth(); // Log.debug(SCROLL_PFX + " drawing up control at (" + x2 + ", " + // y2 // + ") w: " + w2 + " h: " + h2); g2.setPaint(geoList.getBackgroundColor()); g2.fillRoundRect(x2, y2, w2, h2, ROUND, ROUND); dropDown.drawScrollDown(g2, x2, y2, w2, h2, geoList.getBackgroundColor(), false); } private void getOneColumnSettings() { setColCount(1); dimItem = AwtFactory.getPrototype() .newDimension(boxWidth > dimItem.getWidth() ? boxWidth : dimItem.getWidth(), dimItem.getHeight()); } /** * Gets scroll settings: visible items, boundaries. * * @return if scroll really makes sense or multi-column would be better. */ private boolean getScrollSettings() { itemFont = getLabelFont().deriveFont(GFont.PLAIN, itemFontSize); createItems(); getOneColumnSettings(); rectUp = AwtFactory.getPrototype().newRectangle(dimItem.getWidth(), dimItem.getHeight() / 2); rectDown = AwtFactory.getPrototype() .newRectangle(dimItem.getWidth(), dimItem.getHeight() / 2); int maxItems = geoList.size(); int visibleItems = ((viewOpt.getHeight() - (2 * MARGIN)) / dimItem.getHeight()) - 1; if (visibleItems > maxItems) { // can't display more than this visibleItems = maxItems; scrollNeeded = false; } else if (visibleItems < maxItems - 1) { // visibleItems = (viewOpt.getHeight() - (2 * MARGIN + // arrowsHeight)) // / dimItem.getHeight(); // The two additional arrows take an item by height. visibleItems--; scrollNeeded = true; } if (startIdx + visibleItems < maxItems) { endIdx = startIdx + visibleItems + 1; } else { startIdx = maxItems - visibleItems - 1; endIdx = maxItems; } rowCount = getVisibleItemCount(); if (!scrollNeeded) { startIdx = 0; endIdx = maxItems; } // Log.debug( // "[SCROLL] max: " + maxItems + " visible: " + visibleItems); // Log.debug("[SCROLL]" + "startIdx: " + startIdx + " endIdx: " // + endIdx); scrollNeeded = scrollNeeded && rowCount > 2; if (scrollNeeded) { top = getScrollTop(); } return scrollNeeded; } private int getScrollTop() { return MARGIN; } private int getVisibleItemCount() { int result = isScrollNeeded() ? getEndIdx() - getStartIdx() : items.size(); // There will be always one row at least. return result > 0 ? result : 1; } /** * Gets the columns and rows for options table. * * @return true if all table columns are full, ie no column with fewer * items at the end. */ private boolean getTableScale() { int maxItems = geoList.size(); int maxRows = ((viewOpt.getHeight() - 2 * MARGIN) / dimItem.getHeight()) + 1; int maxCols = viewOpt.getWidth() / dimItem.getWidth(); if (maxItems < maxRows) { getOneColumnSettings(); rowCount = maxItems; return true; } setColCount(maxItems / maxRows + (maxItems % maxRows == 0 ? 0 : 1)); rowCount = maxRows; balanceTable(); return (colCount < maxCols); } private void balanceTable() { int itemCount = geoList.size(); int resultRow = 0; int maxMod = 0; boolean found = false; int row = rowCount; while (!found && row > 2) { row--; int mod = itemCount % row; if (mod == 0) { resultRow = row; maxMod = 0; found = true; } else if (mod > maxMod) { maxMod = mod; resultRow = row; } } rowCount = resultRow == 0 ? 1 : resultRow; colCount = itemCount / rowCount + (maxMod == 0 ? 0 : 1); // Log.debug("[BALANCE] mod: " + maxMod + " cols: " + colCount // + " rows: " + rowCount); } private void createItems() { double maxWidth = 0; double maxHeight = 0; items.clear(); for (int i = 0; i < geoList.size(); i++) { OptionItem item = new OptionItem(g2, i); items.add(item); if (maxWidth < item.width) { maxWidth = item.width; } if (maxHeight < item.height) { maxHeight = item.height; } } dimItem = AwtFactory.getPrototype().newDimension( (int) (maxWidth + 2 * xPadding), (int) (maxHeight + 2 * yPadding)); } public boolean isVisible() { return visible; } boolean setVisible(boolean visible) { boolean repaintNeeded = this.visible != visible; this.visible = visible; if (visible) { viewOpt.setOpenedComboBox(DrawDropDownList.this); if (isScrollNeeded()) { int selIdx = geoList.getSelectedIndex(); if (selIdx + getVisibleItemCount() < geoList.size()) { startIdx = selIdx; } else { startIdx = geoList.size() - getVisibleItemCount(); } } else { startIdx = 0; } selectedIndex = startIdx; if (selectedIndex > 0 && selectedIndex < items.size()) { hovered = items.get(selectedIndex); } else { hovered = null; } } viewOpt.repaintView(); updateOpenedComboBox(); // Log.error("repaint 4"); repaintNeeded = true; // instead of: viewOpt.repaintView(); return repaintNeeded; } public void onResize(int w, int h) { int dW = viewWidth - w; int dH = viewHeight - h; if (dW != 0 || dH != 0) { viewHeight = h; viewWidth = w; hovered = null; } } void toggle() { setVisible(!visible); } public void moveSelectorBy(int diff, boolean forward) { boolean update = false; boolean hasHovered = hovered != null; int idx = hasHovered ? hovered.index : 0; if (forward) { if (idx < items.size() - diff) { idx += diff; update = true; if (idx > endIdx - 1) { scrollDown(); } } } else { if (idx > diff - 1) { idx -= diff; update = true; if (idx < startIdx) { scrollUp(); } } } if (update && idx >= 0 && idx < items.size()) { hovered = items.get(idx); selectedIndex = idx; update(); // Log.error("repaint 6"); getView().repaintView(); } } private void cancelDrag() { dragged = null; dragOffset = 0; } public void moveSelectorVertical(boolean moveDown) { cancelDrag(); moveSelectorBy(1, moveDown); } public void moveSelectorHorizontal(boolean moveLeft) { moveSelectorBy(rowCount, moveLeft); } public int getColCount() { return colCount; } public void setColCount(int colCount) { this.colCount = colCount; } public void selectCurrentItem() { geoList.setSelectedIndex(selectedIndex, true); } public int getMaxItemWidth() { return dimItem != null ? dimItem.getWidth() : 0; } private void stopScrolling() { dropDown.stopScrollTimer(); scrollMode = ScrollMode.NONE; } public void onMouseUp(int x, int y) { stopScrolling(); if (dropDown.isClickTimerRunning()) { dropDown.stopClickTimer(); onClick(x, y); } setDragging(false); } } /** * Creates new drawable list * * @param view * view * @param geoList * list */ public DrawDropDownList(EuclidianView view, GeoList geoList) { this.view = view; this.geoList = geoList; geo = geoList; drawOptions = new DrawOptions(view); dropDown = new DropDownList(view.getApplication(), this); ctrlRect = AwtFactory.getPrototype().newRectangle(); update(); } private String getLabelText() { // don't need to worry about labeling options, just check if caption // set or not if (!"".equals(geo.getRawCaption())) { String caption = geo.getCaption(StringTemplate.defaultTemplate); return caption; } // make sure there's something to drag return Unicode.NBSP + Unicode.NBSP + Unicode.NBSP; } @Override final public void update() { isVisible = geo.isEuclidianVisible() && geoList.size() != 0; int fontSize = (int) (view.getFontSize() * geoList.getFontSizeMultiplier()); setLabelFontSize(fontSize); if (!geo.doHighlighting()) { hideWidget(); } // box.setVisible(isVisible); if (!isVisible) { return; } // eg size changed etc labelDesc = getLabelText(); // box.validate(); xLabel = geo.labelOffsetX; yLabel = geo.labelOffsetY; labelRectangle.setBounds(xLabel, yLabel, (int) (getHitRect().getWidth()), (int) (getHitRect().getHeight())); geoList.setTotalWidth(getTotalWidth()); geoList.setTotalHeight(getTotalHeight()); } @Override final public void draw(GGraphics2D g2) { if (isVisible) { drawOnCanvas(g2, ""); } } /** * Returns whether any one of the list items is at the given screen * position. */ @Override final public boolean hit(int x, int y, int hitThreshold) { DrawDropDownList opened = view.getOpenedComboBox(); if (opened != null && opened != this && opened.isOptionsHit(x, y)) { return false; } return super.hit(x, y, hitThreshold) || isControlHit(x, y) || isOptionsHit(x, y); } @Override public boolean isInside(GRectangle rect) { return super.isInside(rect);// || drawOptions.isInside(rect); } @Override public boolean intersectsRectangle(GRectangle rect) { return super.intersectsRectangle(rect); } /** * Returns the bounding box of this DrawPoint in screen coordinates. */ @Override final public GRectangle getBounds() { return getHitRect().getBounds(); } @Override protected void drawWidget(GGraphics2D g2) { updateMetrics(g2); String labelText = getLabelText(); int textLeft = boxLeft + COMBO_TEXT_MARGIN; int textBottom = boxTop + getTextBottom(); GColor bgColor = geo.getBackgroundColor() != null ? geo.getBackgroundColor() : view.getBackgroundCommon(); dropDown.drawSelected(geoList, g2, bgColor, boxLeft, boxTop, boxWidth, boxHeight); if (!geoList.hasScreenLocation() && boxWidth != 0) { geoList.setScreenLocation(xLabel < boxLeft ? xLabel : boxLeft, yLabel < boxTop ? yLabel : boxTop); ScreenLocation sloc = geoList.getScreenLocation(); sloc.initWidth(boxWidth); sloc.initHeight(boxHeight); } g2.setPaint(GColor.LIGHT_GRAY); highlightLabel(g2, latexLabel); g2.setPaint(geo.getObjectColor()); // Draw the selected line if (seLatex) { textBottom = boxTop + (boxHeight - selectedDimension.getHeight()) / 2; } else { textBottom = alignTextToBottom(g2, boxTop, boxHeight, selectedText); } drawSelectedText(g2, textLeft, textBottom, true); drawControl(g2); if (geo.isLabelVisible()) { drawLabel(g2, geoList, labelText); } drawOptions.draw(g2, boxLeft, boxTop + boxHeight + 5); } private int alignTextToBottom(GGraphics2D g2, int top, int height, String text) { int base = (height + getTextDescent(g2, text)) / 2; return top + base + (height - base) / 2; } @Override protected void drawLabel(GGraphics2D g2, GeoElement geo0, String text) { int textBottom = boxTop + getTextBottom(); boolean latex = isLatexString(text); if (latex) { drawLatex(g2, geo0, getLabelFont(), text, xLabel, boxTop + (boxHeight - labelSize.y) / 2); } else { textBottom = boxTop + (boxHeight + getLabelFontSize() - COMBO_TEXT_MARGIN) / 2; g2.setPaint(geo.getObjectColor()); g2.setFont(getLabelFont()); EuclidianStatic.drawIndexedString(view.getApplication(), g2, text, xLabel, textBottom, false); } } @Override protected void highlightLabel(GGraphics2D g2, boolean latex) { if (geo.isLabelVisible() && geo.doHighlighting() && latex) { g2.fillRect(xLabel, boxTop + (boxHeight - labelSize.y) / 2, labelSize.x, labelSize.y); } else { super.highlightLabel(g2, latex); } } private void drawControl(GGraphics2D g2) { g2.setPaint(GColor.BLACK); int left = boxLeft + boxWidth - boxHeight; ctrlRect.setBounds(boxLeft, boxTop, boxWidth, boxHeight); dropDown.drawControl(g2, left, boxTop, boxHeight, boxHeight, geo.getBackgroundColor(), isOptionsVisible()); } @Override protected void calculateBoxBounds(boolean latex) { boxLeft = xLabel + labelSize.x + LABEL_COMBO_GAP; boxTop = latex ? yLabel + (labelSize.y - getPreferredSize().getHeight()) / 2 : yLabel; boxWidth = getPreferredSize().getWidth(); boxHeight = getPreferredSize().getHeight() + COMBO_TEXT_MARGIN; } @Override protected void calculateBoxBounds() { boxLeft = xLabel + LABEL_COMBO_GAP; boxTop = yLabel; boxWidth = getPreferredSize().getWidth(); boxHeight = getPreferredSize().getHeight(); } @Override protected int getTextBottom() { return isLatexString(selectedText) ? boxHeight - selectedHeight / 2 : (getPreferredSize().getHeight() + getMultipliedFontSize()) / 2; } private void updateMetrics(GGraphics2D g2) { drawOptions.onResize(view.getWidth(), view.getHeight()); GeoElement geoItem = geoList.getSelectedElement(); // boolean latex = false; if (needsLatex(geoItem)) { selectedText = geoItem.toLaTeXString(false, StringTemplate.latexTemplate); seLatex = true; } else { selectedText = geoItem .toValueString(StringTemplate.defaultTemplate); seLatex = isLatexString(selectedText); } selectedDimension = drawSelectedText(g2, 0, 0, false); latexLabel = measureLabel(g2, geoList, getLabelText()); labelRectangle.setBounds(boxLeft - 1, boxTop - 1, boxWidth, boxHeight); } /** * @param geoItem * geo * @return whether it should be painted in LaTeX */ static boolean needsLatex(GeoElement geoItem) { return geoItem instanceof FunctionalNVar || (geoItem.isGeoText() && geoItem.isLaTeXDrawableGeo()); } private GDimension drawSelectedText(GGraphics2D g2, int left, int top, boolean draw) { GFont font = getLabelFont(); if (seLatex) { GDimension d = null; d = draw ? drawLatex(g2, geoList, font, selectedText, left, top) : measureLatex(g2, geoList, font, selectedText); return d; } g2.setFont(font); GTextLayout layout = getLayout(g2, selectedText, font); final int w = (int) layout.getBounds().getWidth(); if (draw) { EuclidianStatic.drawIndexedString(view.getApplication(), g2, selectedText, left, top, false); } return AwtFactory.getPrototype().newDimension(w, (int) Math.round(layout.getDescent() + layout.getAscent())); } private int getTriangleControlWidth() { return selectedDimension.getHeight(); } private int getMultipliedFontSize() { return (int) Math.round( ((getLabelFont().getSize() * geoList.getFontSizeMultiplier()))); } /** * @return preferred width of dropdown */ public int getPreferredWidth() { if (selectedDimension == null) { return 0; } int selectedWidth = selectedDimension.getWidth() + (isLatexString(selectedText) ? 0 : 2 * COMBO_TEXT_MARGIN) + getTriangleControlWidth(); int maxItemWidth = drawOptions.getMaxItemWidth(); return (isOptionsVisible() && maxItemWidth > selectedWidth) ? maxItemWidth : selectedWidth; } @Override public GDimension getPreferredSize() { if (selectedDimension == null) { return AwtFactory.getPrototype().newDimension(0, 0); } return AwtFactory.getPrototype().newDimension(getPreferredWidth(), selectedDimension.getHeight() + COMBO_TEXT_MARGIN); } /** * * @return The whole width of the widget including the label. */ public int getTotalWidth() { return labelSize.getX() + getPreferredWidth(); } /** * * @return The height of the combo including the label */ public int getTotalHeight() { int h = labelSize.getY(); return h > boxHeight ? h : boxHeight; } @Override protected void showWidget() { // no widget } @Override protected void hideWidget() { // no widget } /** * Returns if mouse is hit the options or not. * * @param x * mouse x-coord * @param y * mouse y-coord * @return true if options rectangle hit by mouse. */ public boolean isOptionsHit(int x, int y) { return drawOptions.isHit(x, y); } /** * Called when mouse is over options to highlight item. * * @param x * mouse x-coord * @param y * mouse y-coord */ public void onOptionOver(int x, int y) { drawOptions.onMouseOver(x, y); } /** * Called when user presses down the mouse on the widget. * * @param x * Mouse x coordinate. * @param y * Mouse y coordinate. */ public void onMouseDown(int x, int y) { if (drawOptions.onMouseDown(x, y)) { return; } DrawDropDownList opened = view.getOpenedComboBox(); if ((opened == null || !opened.isOptionsHit(x, y)) && isControlHit(x, y)) { boolean visible = isOptionsVisible(); if (!visible) { // make sure keyboard controls work for the dropdown view.requestFocus(); } setOptionsVisible(!visible); } } /** * Open dropdown */ public void openOptions() { setOptionsVisible(false); } /** * Close dropdown * * @return whether repaint is needed */ public boolean closeOptions() { return setOptionsVisible(false); } /** * @param x * mouse x-coord * @param y * mouse y-coord * @return whether control rectangle was hit */ public boolean isControlHit(int x, int y) { return ctrlRect != null && ctrlRect.contains(x, y); } /** * @return whether dropdown is visible */ public boolean isOptionsVisible() { return drawOptions.isVisible(); } /** * @param optionsVisible * change visibility of dropdown items */ private boolean setOptionsVisible(boolean optionsVisible) { return drawOptions.setVisible(optionsVisible); } /** * toggle visibility of dropdown items */ public void toggleOptions() { drawOptions.toggle(); } /** * Sync selected index to GeoList */ public void selectCurrentItem() { if (!isOptionsVisible()) { return; } drawOptions.selectCurrentItem(); closeOptions(); } /** * Gets DrawList for geo. No type check. * * @param app * The current application. * @param geo * The geo we like to get the DrawList for. * @return The DrawList for the geo element; * */ public static DrawDropDownList asDrawable(App app, GeoElement geo) { return (DrawDropDownList) app.getActiveEuclidianView() .getDrawableFor(geo); } /** * Moves dropdown selector up or down by one item. * * @param down * Sets if selection indicator should move down or up. */ public void moveSelectorVertical(boolean down) { drawOptions.moveSelectorVertical(down); } /** * Moves the selector horizontally, if dropdown have more columns than one. * * @param left * Indicates that selector should move left or right. */ public void moveSelectorHorizontal(boolean left) { drawOptions.moveSelectorHorizontal(left); } /** * @return if combo have more columns than one. */ public boolean isMultiColumn() { return drawOptions.getColCount() > 1; } /** * * @return if list when draw as combo, is selected. */ public boolean isSelected() { return geo.doHighlighting(); } /** * @param x * drag end x * @param y * drag end y * @return whether scroll was needed */ public boolean onDrag(int x, int y) { return drawOptions.onDrag(x, y); } @Override public void onScroll(int x, int y) { drawOptions.scroll(); } /** * @param delta * wheel scroll value; only sign matters */ public void onMouseWheel(double delta) { if (delta > 0) { drawOptions.scrollDown(); } else { drawOptions.scrollUp(); } } @Override public void onClick(int x, int y) { drawOptions.onClick(x, y); } /** * Update the opened comobox variable for enclosing view */ void updateOpenedComboBox() { DrawDropDownList dl = view.getOpenedComboBox(); if (drawOptions.isVisible()) { view.setOpenedComboBox(this); } else if (dl == this) { view.setOpenedComboBox(null); } } /** * @param x * mouse x (within view) * @param y * mouse y (within view) */ public void onMouseUp(int x, int y) { drawOptions.onMouseUp(x, y); } @Override public BoundingBox getBoundingBox() { // TODO Auto-generated method stub return null; } @Override public void updateBoundingBox() { // TODO Auto-generated method stub } }