GitHub Viewer
/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/*
Part of the Processing project - http://processing.org
Copyright (c) 2013-15 The Processing Foundation
Copyright (c) 2004-13 Ben Fry and Casey Reas
Copyright (c) 2001-04 Massachusetts Institute of Technology
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; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package processing.app;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.GeneralPath;
import java.util.Arrays;
import javax.swing.*;
/**
* Sketch tabs at the top of the editor window.
*/
public class EditorHeader extends JComponent {
// height of this tab bar
static final int HIGH = 30;
// standard UI sizing (OS-specific, but generally consistent)
// static final int SCROLLBAR_WIDTH = 16;
// amount of space on the left edge before the tabs start
static final int MARGIN_WIDTH = Editor.LEFT_GUTTER;
static final int ARROW_TAB_WIDTH = 23;
// distance from the righthand side of a tab to the drop-down arrow
// static final int ARROW_GAP_WIDTH = 8;
// indent x/y for notch on the tab
static final int ARROW_TOP = 12;
static final int ARROW_BOTTOM = 20;
static final int ARROW_WIDTH = 9;
// static final int NOTCH = 0;
// how far to raise the tab from the bottom of this Component
static final int TAB_HEIGHT = HIGH;
// // line that continues across all of the tabs for the current one
// static final int TAB_STRETCH = 3;
// amount of extra space between individual tabs
static final int TAB_BETWEEN = 4;
// amount of margin on the left/right for the text on the tab
static final int TEXT_MARGIN = 16;
// width of the tab when no text visible
// (total tab width will be this plus TEXT_MARGIN*2)
static final int NO_TEXT_WIDTH = 16;
Color bgColor;
// boolean hiding;
Color hideColor;
Color textColor[] = new Color[2];
Color tabColor[] = new Color[2];
Color modifiedColor;
Color arrowColor;
Editor editor;
Tab[] tabs = new Tab[0];
Tab[] visitOrder;
Font font;
// FontMetrics metrics;
int fontAscent;
JMenu menu;
JPopupMenu popup;
int menuLeft;
int menuRight;
//
// static final String STATUS[] = { "unsel", "sel" };
static final int UNSELECTED = 0;
static final int SELECTED = 1;
// static final String WHERE[] = { "left", "mid", "right" }; //, "menu" };
// static final int LEFT = 0;
// static final int MIDDLE = 1;
// static final int RIGHT = 2;
// static final int MENU = 3;
// static final int PIECE_WIDTH = 4;
// static final int PIECE_HEIGHT = 33;
// Image[][] pieces;
// static final int ARROW_WIDTH = 14;
// static final int ARROW_HEIGHT = 14;
// static Image tabArrow;
//
Image offscreen;
int sizeW, sizeH;
int imageW, imageH;
String lastNoticeName;
Image gradient;
public EditorHeader(Editor eddie) {
this.editor = eddie;
gradient = editor.getMode().getGradient("header", 400, HIGH);
updateMode();
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
int x = e.getX();
int y = e.getY();
if ((x > menuLeft) && (x < menuRight)) {
popup.show(EditorHeader.this, x, y);
} else {
Sketch sketch = editor.getSketch();
// for (int i = 0; i < sketch.getCodeCount(); i++) {
// if ((x > tabLeft[i]) && (x < tabRight[i])) {
// sketch.setCurrentCode(i);
// repaint();
// }
// }
for (Tab tab : tabs) {
if (tab.contains(x)) {
sketch.setCurrentCode(tab.index);
repaint();
}
}
}
}
public void mouseExited(MouseEvent e) {
// only clear if it's been set
if (lastNoticeName != null) {
// only clear if it's the same as what we set it to
editor.clearNotice(lastNoticeName);
lastNoticeName = null;
}
}
});
addMouseMotionListener(new MouseMotionAdapter() {
public void mouseMoved(MouseEvent e) {
int x = e.getX();
for (Tab tab : tabs) {
if (tab.contains(x) && !tab.textVisible) {
lastNoticeName = editor.getSketch().getCode(tab.index).getPrettyName();
editor.statusNotice(lastNoticeName);
}
}
}
});
}
// protected String tabFile(int status, int where) {
// return "theme/tab-" + STATUS[status] + "-" + WHERE[where];
// }
public void updateMode() {
Mode mode = editor.getMode();
// if (tabArrow == null) {
// String suffix = Toolkit.highResDisplay() ? "-2x.png" : ".png";
// tabArrow = Toolkit.getLibImage("tab-arrow" + suffix);
// }
bgColor = mode.getColor("header.bgcolor");
textColor[SELECTED] = mode.getColor("header.text.selected.color");
textColor[UNSELECTED] = mode.getColor("header.text.unselected.color");
font = mode.getFont("header.text.font");
tabColor[SELECTED] = mode.getColor("header.tab.selected.color");
tabColor[UNSELECTED] = mode.getColor("header.tab.unselected.color");
arrowColor = mode.getColor("header.tab.arrow.color");
modifiedColor = mode.getColor("editor.selection.color");
}
public void paintComponent(Graphics screen) {
setOpaque(false);
if (screen == null) return;
Sketch sketch = editor.getSketch();
if (sketch == null) return; // ??
Dimension size = getSize();
if ((size.width != sizeW) || (size.height != sizeH)) {
// component has been resized
if ((size.width > imageW) || (size.height > imageH)) {
// nix the image and recreate, it's too small
offscreen = null;
} else {
// if the image is larger than necessary, no need to change
sizeW = size.width;
sizeH = size.height;
}
}
if (offscreen == null) {
sizeW = size.width;
sizeH = size.height;
imageW = sizeW;
imageH = sizeH;
if (Toolkit.highResDisplay()) {
offscreen = createImage(imageW*2, imageH*2);
} else {
offscreen = createImage(imageW, imageH);
}
}
Graphics g = offscreen.getGraphics();
g.setFont(font); // need to set this each time through
if (fontAscent == 0) {
fontAscent = (int) Toolkit.getAscent(g);
}
Graphics2D g2 = Toolkit.prepareGraphics(g);
g.drawImage(gradient, 0, 0, imageW, imageH, this);
if (tabs.length != sketch.getCodeCount()) {
tabs = new Tab[sketch.getCodeCount()];
for (int i = 0; i < tabs.length; i++) {
tabs[i] = new Tab(i);
}
visitOrder = new Tab[sketch.getCodeCount() - 1];
}
int leftover = TAB_BETWEEN + ARROW_TAB_WIDTH; // + MARGIN_WIDTH; // + SCROLLBAR_WIDTH;
int tabMax = getWidth() - leftover;
// reset all tab positions
for (Tab tab : tabs) {
SketchCode code = sketch.getCode(tab.index);
tab.textVisible = true;
tab.lastVisited = code.lastVisited();
// hide extensions for .pde files (or whatever else is the norm elsewhere
boolean hide = editor.getMode().hideExtension(code.getExtension());
// String codeName = hide ? code.getPrettyName() : code.getFileName();
// if modified, add the li'l glyph next to the name
// tab.text = " " + codeName + (code.isModified() ? " \u00A7" : " ");
// tab.text = " " + codeName + " ";
tab.text = hide ? code.getPrettyName() : code.getFileName();
tab.textWidth = (int)
font.getStringBounds(tab.text, g2.getFontRenderContext()).getWidth();
}
// make sure everything can fit
if (!placeTabs(MARGIN_WIDTH, tabMax, null)) {
//System.arraycopy(tabs, 0, visitOrder, 0, tabs.length);
// always show the tab with the sketch's name
// System.arraycopy(tabs, 1, visitOrder, 0, tabs.length - 1);
int index = 0;
// stock the array backwards so that the rightmost tabs are closed by default
for (int i = tabs.length - 1; i > 0; --i) {
visitOrder[index++] = tabs[i];
}
Arrays.sort(visitOrder); // sort on when visited
// for (int i = 0; i < visitOrder.length; i++) {
// System.out.println(visitOrder[i].index + " " + visitOrder[i].text);
// }
// System.out.println();
// Keep shrinking the tabs one-by-one until things fit properly
for (int i = 0; i < visitOrder.length; i++) {
tabs[visitOrder[i].index].textVisible = false;
if (placeTabs(MARGIN_WIDTH, tabMax, null)) {
break;
}
}
}
// now actually draw the tabs
placeTabs(MARGIN_WIDTH, tabMax, g2);
// draw the dropdown menu target
menuLeft = tabs[tabs.length - 1].right + TAB_BETWEEN;
menuRight = menuLeft + ARROW_TAB_WIDTH;
g.setColor(tabColor[UNSELECTED]);
drawTab(g, menuLeft, menuRight);
// int arrowY = (getHeight() - TAB_HEIGHT - TAB_STRETCH) + (TAB_HEIGHT - ARROW_HEIGHT)/2;
// g.drawImage(tabArrow, menuLeft, arrowY,
// ARROW_WIDTH, ARROW_HEIGHT, null);
// TODO draw arrow here
g.setColor(arrowColor);
GeneralPath trianglePath = new GeneralPath();
float x1 = menuLeft + (ARROW_TAB_WIDTH - ARROW_WIDTH) / 2f;
float x2 = menuLeft + (ARROW_TAB_WIDTH + ARROW_WIDTH) / 2f;
trianglePath.moveTo(x1, ARROW_TOP);
trianglePath.lineTo(x2, ARROW_TOP);
trianglePath.lineTo((x1 + x2) / 2, ARROW_BOTTOM);
trianglePath.closePath();
g2.fill(trianglePath);
screen.drawImage(offscreen, 0, 0, imageW, imageH, null);
}
private boolean placeTabs(int left, int right, Graphics2D g) {
Sketch sketch = editor.getSketch();
int x = left;
final int bottom = getHeight(); // - TAB_STRETCH;
final int top = bottom - TAB_HEIGHT;
// GeneralPath path = null;
for (int i = 0; i < sketch.getCodeCount(); i++) {
SketchCode code = sketch.getCode(i);
Tab tab = tabs[i];
// int pieceCount = 2 + (tab.textWidth / PIECE_WIDTH);
// if (tab.textVisible == false) {
// pieceCount = 4;
// }
// int pieceWidth = pieceCount * PIECE_WIDTH;
int state = (code == sketch.getCurrentCode()) ? SELECTED : UNSELECTED;
// if (g != null) {
// //g.drawImage(pieces[state][LEFT], x, 0, PIECE_WIDTH, PIECE_HEIGHT, null);
// path = new GeneralPath();
// path.moveTo(x, bottom);
// path.lineTo(x, top + NOTCH);
// path.lineTo(x + NOTCH, top);
// }
tab.left = x;
x += TEXT_MARGIN;
// x += PIECE_WIDTH;
// int contentLeft = x;
// for (int j = 0; j < pieceCount; j++) {
// if (g != null) {
// g.drawImage(pieces[state][MIDDLE], x, 0, PIECE_WIDTH, PIECE_HEIGHT, null);
// }
// x += PIECE_WIDTH;
// }
// if (g != null) {
int drawWidth = tab.textVisible ? tab.textWidth : NO_TEXT_WIDTH;
x += drawWidth + TEXT_MARGIN;
// path.moveTo(x, top);
// }
tab.right = x;
if (g != null) {
g.setColor(tabColor[state]);
drawTab(g, tab.left, tab.right);
// path.lineTo(x - NOTCH, top);
// path.lineTo(x, top + NOTCH);
// path.lineTo(x, bottom);
// path.closePath();
// g.setColor(tabColor[state]);
// g.fill(path);
// // have to draw an extra outline to make things line up on retina
// g.draw(path);
// //g.drawImage(pieces[state][RIGHT], x, 0, PIECE_WIDTH, PIECE_HEIGHT, null);
if (tab.textVisible) {
int textLeft = tab.left + ((tab.right - tab.left) - tab.textWidth) / 2;
g.setColor(textColor[state]);
// int baseline = (int) Math.ceil((sizeH + fontAscent) / 2.0);
//int baseline = bottom - (TAB_HEIGHT - fontAscent)/2;
int tabHeight = TAB_HEIGHT; //bottom - top;
int baseline = top + (tabHeight + fontAscent) / 2;
//g.drawString(sketch.code[i].name, textLeft, baseline);
g.drawString(tab.text, textLeft, baseline);
// g.drawLine(tab.left, baseline-fontAscent, tab.right, baseline-fontAscent);
// g.drawLine(tab.left, baseline, tab.right, baseline);
}
if (code.isModified()) {
g.setColor(modifiedColor);
//g.drawLine(tab.left + NOTCH, top, tab.right - NOTCH, top);
g.drawLine(tab.left, top, tab.right, top);
}
}
// if (g != null) {
// g.drawImage(pieces[state][RIGHT], x, 0, PIECE_WIDTH, PIECE_HEIGHT, null);
// }
// x += PIECE_WIDTH - 1; // overlap by 1 pixel
x += TAB_BETWEEN;
}
// removed 150130
// // Draw this last because of half-pixel overlaps on retina displays
// if (g != null) {
// g.setColor(tabColor[SELECTED]);
// g.fillRect(0, bottom, getWidth(), TAB_STRETCH);
// }
return x 1);
}
menu.add(item);
item = Editor.newJMenuItem("Next", KeyEvent.VK_PAGE_DOWN);
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("ext");
}
});
if (editor.sketch != null) {
item.setEnabled(editor.sketch.codeCount > 1);
}
menu.add(item);
menu.addSeparator();
*/
//item = new JMenuItem("New Tab");
item = Toolkit.newJMenuItemShift(Language.text("editor.header.new_tab"), KeyEvent.VK_N);
action = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
editor.getSketch().handleNewCode();
}
};
mapKey = "editor.header.new_tab";
keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_N, Toolkit.SHORTCUT_SHIFT_KEY_MASK);
mInputMap.put(keyStroke, mapKey);
mActionMap.put(mapKey, action);
item.addActionListener(action);
menu.add(item);
item = new JMenuItem(Language.text("editor.header.rename"));
action = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
editor.getSketch().handleRenameCode();
/*
// this is already being called by nameCode(), the second stage of rename
if (editor.sketch.current == editor.sketch.code[0]) {
editor.sketchbook.rebuildMenus();
}
*/
}
};
item.addActionListener(action);
menu.add(item);
item = Toolkit.newJMenuItemShift(Language.text("editor.header.delete"), KeyEvent.VK_D);
action = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
Sketch sketch = editor.getSketch();
if (!Base.isMacOS() && // ok on OS X
editor.base.editors.size() == 1 && // mmm! accessor
sketch.getCurrentCodeIndex() == 0) {
Base.showWarning(Language.text("editor.header.delete.warning.title"),
Language.text("editor.header.delete.warning.text"), null);
} else {
editor.getSketch().handleDeleteCode();
}
}
};
mapKey = "editor.header.delete";
keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_D, Toolkit.SHORTCUT_ALT_KEY_MASK);
mInputMap.put(keyStroke, mapKey);
mActionMap.put(mapKey, action);
item.addActionListener(action);
menu.add(item);
menu.addSeparator();
// KeyEvent.VK_LEFT and VK_RIGHT will make Windows beep
item = Toolkit.newJMenuItemAlt(Language.text("editor.header.previous_tab"), KeyEvent.VK_LEFT);
action = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
// Sketch sketch = editor.getSketch();
// sketch.setCurrentCode(sketch.getCurrentCodeIndex() - 1);
editor.getSketch().handlePrevCode();
}
};
mapKey = "editor.header.previous_tab";
keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, Toolkit.SHORTCUT_ALT_KEY_MASK);
mInputMap.put(keyStroke, mapKey);
mActionMap.put(mapKey, action);
item.addActionListener(action);
menu.add(item);
item = Toolkit.newJMenuItemAlt(Language.text("editor.header.next_tab"), KeyEvent.VK_RIGHT);
action = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
// Sketch sketch = editor.getSketch();
// sketch.setCurrentCode(sketch.getCurrentCodeIndex() + 1);
editor.getSketch().handleNextCode();
}
};
mapKey = "editor.header.next_tab";
keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, Toolkit.SHORTCUT_ALT_KEY_MASK);
mInputMap.put(keyStroke, mapKey);
mActionMap.put(mapKey, action);
item.addActionListener(action);
menu.add(item);
Sketch sketch = editor.getSketch();
if (sketch != null) {
menu.addSeparator();
ActionListener jumpListener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
editor.getSketch().setCurrentCode(e.getActionCommand());
}
};
for (SketchCode code : sketch.getCode()) {
item = new JMenuItem(code.getPrettyName());
item.addActionListener(jumpListener);
menu.add(item);
}
}
Toolkit.setMenuMnemonics(menu);
}
public void deselectMenu() {
repaint();
}
public Dimension getPreferredSize() {
return new Dimension(300, HIGH);
}
public Dimension getMinimumSize() {
return getPreferredSize();
}
public Dimension getMaximumSize() {
return new Dimension(super.getMaximumSize().width, HIGH);
}
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
static class Tab implements Comparable {
int index;
int left;
int right;
String text;
int textWidth;
boolean textVisible;
long lastVisited;
Tab(int index) {
this.index = index;
}
boolean contains(int x) {
return x >= left && x