| /******************************************************************************* |
| * Copyright (c) 2011 University of Illinois at Urbana-Champaign and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * UIUC - Initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.photran.internal.ui.editor; |
| |
| import org.eclipse.core.runtime.Preferences.IPropertyChangeListener; |
| import org.eclipse.core.runtime.Preferences.PropertyChangeEvent; |
| import org.eclipse.jface.text.source.IVerticalRuler; |
| import org.eclipse.photran.internal.core.FortranCorePlugin; |
| import org.eclipse.photran.internal.core.preferences.FortranPreferences; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.custom.CaretEvent; |
| import org.eclipse.swt.custom.CaretListener; |
| import org.eclipse.swt.custom.StyledText; |
| import org.eclipse.swt.events.ControlEvent; |
| import org.eclipse.swt.events.ControlListener; |
| import org.eclipse.swt.events.PaintEvent; |
| import org.eclipse.swt.events.PaintListener; |
| import org.eclipse.swt.events.SelectionEvent; |
| import org.eclipse.swt.events.SelectionListener; |
| import org.eclipse.swt.graphics.Font; |
| import org.eclipse.swt.graphics.GC; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.layout.GridLayout; |
| import org.eclipse.swt.widgets.Composite; |
| |
| /** |
| * Ruler displaying column numbers at the top of the Fortran editor. |
| * |
| * @author Jeff Overbey |
| * |
| * @see FortranSourceViewer#createControl(Composite, int) |
| */ |
| @SuppressWarnings("deprecation") |
| public final class HorizontalRuler extends Composite implements PaintListener, CaretListener, SelectionListener, ControlListener, IPropertyChangeListener |
| { |
| private IVerticalRuler verticalRuler = null; |
| private StyledText styledText = null; |
| |
| public HorizontalRuler(Composite parent, int style) |
| { |
| super(parent, style); |
| |
| GridLayout layout = new GridLayout(1, false); |
| layout.horizontalSpacing = 0; |
| layout.verticalSpacing = 0; |
| layout.marginWidth = layout.marginHeight = 0; |
| setLayout(layout); |
| |
| addPaintListener(this); |
| |
| FortranCorePlugin.getDefault().getPluginPreferences().addPropertyChangeListener(this); |
| } |
| |
| public void configure(IVerticalRuler verticalRuler, StyledText styledText) |
| { |
| this.verticalRuler = verticalRuler; |
| this.styledText = styledText; |
| styledText.addCaretListener(this); |
| styledText.getHorizontalBar().addSelectionListener(this); |
| verticalRuler.getControl().addControlListener(this); |
| } |
| |
| public void paintControl(PaintEvent e) |
| { |
| if (verticalRuler != null && styledText != null && !styledText.isDisposed()) |
| new RulerPainter().paint(e.gc); |
| } |
| |
| private final class RulerPainter |
| { |
| private int height = getSize().y; |
| private int controlWidth = getSize().x; |
| private int left = verticalRuler.getWidth() - styledText.getHorizontalPixel(); |
| private int verticalRulerWidth = verticalRuler.getWidth(); |
| private int scrollBarWidth = styledText.getVerticalBar().getSize().x; |
| |
| private int characterWidth; |
| private int numCharsScrolled; |
| private int rulerWidth; |
| |
| public void paint(GC gc) |
| { |
| gc.setFont(styledText.getFont()); |
| |
| characterWidth = gc.getFontMetrics().getAverageCharWidth(); |
| numCharsScrolled = styledText.getHorizontalIndex(); |
| rulerWidth = controlWidth - scrollBarWidth - left + numCharsScrolled*characterWidth; |
| |
| drawBackground(gc); |
| if (isFixedWidthFont(gc)) |
| { |
| drawTicks(gc); |
| drawTabs(gc); |
| drawNumbers(gc); |
| } |
| drawCursorPosition(gc); |
| } |
| |
| private void drawBackground(GC gc) |
| { |
| gc.setBackground(gc.getDevice().getSystemColor(SWT.COLOR_GRAY)); |
| gc.setForeground(gc.getDevice().getSystemColor(SWT.COLOR_WHITE)); |
| |
| gc.fillGradientRectangle(0, 0, controlWidth, height*2, true); |
| } |
| |
| /* Character Widths (Windows): |
| * Font Name w i Avg (Height) |w-Avg|/Ht |Avg-i|/Ht |
| * |
| * Consolas 10pt 7 6 7 (15) 0 .06 |
| * Consolas 18pt 13 10 13 (18) 0 .17 |
| * Courier New 10pt 8 6 8 (16) 0 .125 |
| * Courier New 18pt 14 10 14 (27) 0 .148 |
| * |
| * Times New Roman 10pt 9 3 5 (15) .27 .4 |
| * Times New Roman 18pt 17 6 10 (27) .26 .4 |
| * |
| * So, heuristically, this method classifies a font as fixed width iff |
| * (1) the absolute difference between the width of w and the average |
| * character width is less than 20% of the font height |
| * (2) the absolute difference between the width of i and the average |
| * character width is less than 20% of the font height |
| */ |
| private boolean isFixedWidthFont(GC gc) |
| { |
| //System.out.println("Width of w: " + gc.getCharWidth('w')); |
| //System.out.println("Width of i: " + gc.getCharWidth('i')); |
| //System.out.println("Avg Width: " + gc.getFontMetrics().getAverageCharWidth()); |
| //System.out.println("Height: " + gc.getFontMetrics().getHeight()); |
| |
| final int avgCharWidth = gc.getFontMetrics().getAverageCharWidth(); |
| final float fontHeight = gc.getFontMetrics().getHeight(); |
| return Math.abs(gc.getCharWidth('w') - avgCharWidth) < .2 * fontHeight |
| && Math.abs(gc.getCharWidth('i') - avgCharWidth) < .2 * fontHeight; |
| } |
| |
| private void drawTicks(GC gc) |
| { |
| gc.setForeground(gc.getDevice().getSystemColor(SWT.COLOR_DARK_GRAY)); |
| |
| for (int i = 0; i <= rulerWidth/characterWidth; i++) |
| { |
| int x = left + i*characterWidth - characterWidth/2 + 1; |
| |
| int tickHeight; |
| if (i % 10 == 0) |
| tickHeight = 0; |
| else if (i % 5 == 0) |
| tickHeight = 3; |
| else |
| tickHeight = 1; |
| |
| if (x >= verticalRulerWidth && tickHeight > 0) |
| { |
| int center = height / 2; |
| int y1 = center - tickHeight + 1; |
| int y2 = center + tickHeight - 1; |
| gc.drawLine(x, y1, x, y2); |
| } |
| } |
| } |
| |
| private void drawTabs(GC gc) |
| { |
| gc.setBackground(gc.getDevice().getSystemColor(SWT.COLOR_GRAY)); |
| |
| int tabSize = FortranPreferences.TAB_WIDTH.getValue(); |
| |
| for (int i = tabSize+1; i <= rulerWidth/characterWidth; i += tabSize) |
| { |
| int x = left + i*characterWidth - characterWidth/2 + 1; |
| int y = height-1; |
| if (x >= verticalRulerWidth) |
| { |
| gc.fillPolygon(new int[] |
| { |
| x, y-3, |
| x-3, y, |
| x+3, y |
| }); |
| } |
| } |
| } |
| |
| private void drawNumbers(GC gc) |
| { |
| gc.setForeground(gc.getDevice().getSystemColor(SWT.COLOR_DARK_GRAY)); |
| |
| Font origFont = gc.getFont(); |
| gc.setFont(gc.getDevice().getSystemFont()); |
| for (int i = 10; i <= rulerWidth/characterWidth; i += 10) |
| { |
| String string = Integer.toString(i/10); |
| string = string.substring(string.length()-1); |
| Point extent = gc.textExtent(string); |
| int x = left + i*characterWidth - (extent.x / 2) - 1; |
| int y = (height - extent.y) / 2; |
| if (x >= verticalRulerWidth) |
| gc.drawText(string, x, y, true); |
| } |
| gc.setFont(origFont); |
| } |
| |
| private void drawCursorPosition(GC gc) |
| { |
| gc.setForeground(gc.getDevice().getSystemColor(SWT.COLOR_GRAY)); |
| |
| Point caretLocation = styledText.getCaret().getLocation(); |
| int rectWidth = isFixedWidthFont(gc) |
| ? gc.getFontMetrics().getAverageCharWidth() |
| : styledText.getCaret().getSize().x; |
| gc.drawRectangle( |
| verticalRulerWidth+caretLocation.x, |
| 0, |
| rectWidth, |
| height-1); |
| } |
| } |
| |
| public void caretMoved(CaretEvent event) |
| { |
| redraw(); |
| } |
| |
| public void widgetSelected(SelectionEvent e) |
| { |
| redraw(); |
| } |
| |
| public void widgetDefaultSelected(SelectionEvent e) |
| { |
| redraw(); |
| } |
| |
| public void controlMoved(ControlEvent e) |
| { |
| redraw(); |
| } |
| |
| public void controlResized(ControlEvent e) |
| { |
| redraw(); |
| } |
| |
| public void propertyChange(PropertyChangeEvent event) |
| { |
| if (!isDisposed() && getDisplay().getThread() == Thread.currentThread()) |
| { |
| redraw(); |
| } |
| } |
| |
| @Override |
| public void dispose() |
| { |
| FortranCorePlugin.getDefault().getPluginPreferences().removePropertyChangeListener(this); |
| super.dispose(); |
| } |
| } |