From: lempinen Date: Fri, 2 Mar 2012 14:35:05 +0000 (+0000) Subject: First implementation of font changing controls (refs #2959) X-Git-Tag: simantics-1.6~31 X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=commitdiff_plain;h=f7a0ddc872055f03d3cd215525ec391bc0e1b044;p=simantics%2Fsysdyn.git First implementation of font changing controls (refs #2959) git-svn-id: https://www.simantics.org/svn/simantics/sysdyn/trunk@24332 ac1ea38d-2e2b-0410-8846-a27921b304fc --- diff --git a/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/properties/widgets/FontSelectionComposite.java b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/properties/widgets/FontSelectionComposite.java new file mode 100644 index 00000000..fd3b4a3b --- /dev/null +++ b/org.simantics.sysdyn.ui/src/org/simantics/sysdyn/ui/properties/widgets/FontSelectionComposite.java @@ -0,0 +1,668 @@ +/******************************************************************************* + * Copyright (c) 2007, 2012 Association for Decentralized Information Management in + * Industry THTH ry. + * 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: + * VTT Technical Research Centre of Finland - initial API and implementation + *******************************************************************************/ +package org.simantics.sysdyn.ui.properties.widgets; + +import java.awt.Font; +import java.awt.GraphicsEnvironment; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.TreeMap; + +import org.eclipse.core.runtime.ListenerList; +import org.eclipse.jface.layout.GridDataFactory; +import org.eclipse.jface.layout.GridLayoutFactory; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.FontData; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.ScrollBar; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; +import org.eclipse.swt.widgets.TableItem; +import org.eclipse.swt.widgets.Text; +import org.simantics.browsing.ui.swt.widgets.impl.WidgetSupport; +import org.simantics.db.management.ISessionContext; + +/** + * Composite for displaying font selection tools. By default, the composite contains + * font family, font style and font size. + * + * @author Teemu Lempinen + * + */ +public class FontSelectionComposite extends Composite { + + + protected Text fontName, fontStyle, fontSize; + protected ArrayList familyIndex = new ArrayList(); + protected TreeMap> fonts = getFonts(familyIndex); + protected Table fontFamilyTable, fontStyleTable, fontSizeTable; + protected String[] sizes = new String[] {"8", "9", "10", "11", "12", "14", "16", "18", "20", "24", "26", "28", "36", "48", "72"}; + + private ListenerList modifyListeners; + + + /** + * Gets all available fonts + * @param familyIndex Optional list for indexing font families + * @return Tree where key is font family name and object a list of fonts belonging to that family + */ + private static TreeMap> getFonts(ArrayList familyIndex) { + TreeMap> fonts = new TreeMap>(); + + GraphicsEnvironment gEnv = GraphicsEnvironment + .getLocalGraphicsEnvironment(); + Font allFonts[] = gEnv.getAllFonts(); + + for(Font font : allFonts) { + String family = font.getFamily(Locale.ROOT); + if(!fonts.containsKey(family)) { + if(familyIndex != null) + familyIndex.add(family); + fonts.put(family, new ArrayList()); + } + + boolean add = true; + for(Font f : fonts.get(family)) { + if(f.getFontName().equals(font.getFontName())) { + add = false; + break; + } + } + + if(add) + fonts.get(family).add(font); + } + return fonts; + } + + /** + * Composite containing components for selecting a font + * + * @param parent Parent composite + * @param context {@link ISessionContext} + * @param support {@link WidgetSupport} + * @param style SWT style + */ + public FontSelectionComposite(Composite parent, ISessionContext context, WidgetSupport support, int style) { + super(parent, style); + + modifyListeners = new ListenerList(); + + GridLayoutFactory.fillDefaults().numColumns(3).applyTo(this); + + /* + * Two-row layout. First row consists of editable text boxes, + * second row consists of tables containing possible options + */ + + // First row + fontName = new Text(this, SWT.BORDER); + GridDataFactory.fillDefaults().applyTo(fontName); + + fontStyle = new Text(this, SWT.BORDER); + GridDataFactory.fillDefaults().applyTo(fontStyle); + + fontSize = new Text(this, SWT.BORDER); + GridDataFactory.fillDefaults().applyTo(fontSize); + + // Second row + fontFamilyTable = new Table (this, SWT.VIRTUAL | SWT.BORDER | SWT.FULL_SELECTION); + fontFamilyTable.setLinesVisible (false); + fontFamilyTable.setHeaderVisible (false); + GridDataFactory.fillDefaults().hint(SWT.DEFAULT, 100).applyTo(fontFamilyTable); + fontFamilyTable.setItemCount(familyIndex.size()); + TableColumn column = new TableColumn (fontFamilyTable, SWT.NONE); + column.setWidth(200); + + + fontStyleTable = new Table (this, SWT.BORDER | SWT.FULL_SELECTION); + fontStyleTable.setLinesVisible (false); + fontStyleTable.setHeaderVisible (false); + GridDataFactory.fillDefaults().hint(100, 100).applyTo(fontStyleTable); + column = new TableColumn (fontStyleTable, SWT.NONE); + setFontStyleTableWidth(); + + + + fontSizeTable = new Table (this, SWT.VIRTUAL | SWT.BORDER | SWT.FULL_SELECTION); + fontSizeTable.setLinesVisible (false); + fontSizeTable.setHeaderVisible (false); + GridDataFactory.fillDefaults().hint(SWT.DEFAULT, 100).applyTo(fontSizeTable); + column = new TableColumn (fontSizeTable, SWT.NONE); + column.setWidth(70); + for(String size : sizes) { + TableItem item = new TableItem(fontSizeTable, SWT.NONE); + item.setText(0, size); + } + + // Listeners for components + addFontFamilyListeners(); + addFontStyleListeners(); + addFontSizeListeners(); + } + + + /** + * Set controls to display given font + */ + public void setFont(Font font, boolean notify) { + Object[] listeners = new Object[0]; + if(!notify) { + listeners = modifyListeners.getListeners(); + for(Object listener : listeners) + modifyListeners.remove(listener); + } + + String fontFamily = font.getFamily(Locale.ROOT); + String fontName = font.getFontName(Locale.ROOT); + this.fontName.setText(fontFamily); + this.fontFamilyTable.setTopIndex(this.fontFamilyTable.getSelectionIndex()); + + String style = "Regular"; + if(fontName.length() > fontFamily.length()) + style = fontName.substring(fontFamily.length() + 1); + this.fontStyle.setText(style); + + int size = font.getSize(); + fontSize.setText("" + size); + + if(!notify) { + for(Object listener : listeners) + modifyListeners.add(listener); + } + } + + /** + * Get the AWT font defined in this composite + * @return AWT font + */ + public Font getAWTFont() { + String family = fontName.getText(); + String style = fontStyle.getText(); + if(style.equals("Regular")) + style = null; + + String name = family + (style != null ? " " + style : ""); + + int size = 10; + try { + size = Integer.parseInt(fontSize.getText()); + } catch (NumberFormatException e) { + } + + if(name != null && name.length() > 0) + return new Font(name, 0, size); + else + return null; + } + + + /** + * Adds listeners for font family name text and table + */ + protected void addFontFamilyListeners() { + + // Font name modify listener + fontName.addModifyListener(new ModifyListener() { + + @Override + public void modifyText(ModifyEvent e) { + Text text = (Text) e.widget; + String name = text.getText(); + fontFamilyTextModified(name, false); + } + }); + + // Font name key listener for auto-completion + fontName.addKeyListener(new KeyListener() { + + @Override + public void keyReleased(KeyEvent e) { + } + + @Override + public void keyPressed(KeyEvent e) { + if ((e.character == ' ') && ((e.stateMask & SWT.CTRL) != 0) ) { + fontFamilyTextModified(fontName.getText(), true); + e.doit = false; + } else if(e.keyCode == SWT.CR || e.keyCode == SWT.LF || e.keyCode == SWT.KEYPAD_CR) { + fontChanged(); + } + } + }); + + // Call listener when editing has ended + fontName.addListener(SWT.FocusOut, new Listener() { + public void handleEvent(Event e) { + fontChanged(); + } + }); + + // Font family data listener for lazy initialization of the table + fontFamilyTable.addListener (SWT.SetData, new Listener () { + public void handleEvent (Event event) { + TableItem item = (TableItem) event.item; + int index = fontFamilyTable.indexOf (item); + + String family = familyIndex.get(index); + item.setText (family); + + Font font = fonts.get(family).get(0); + if(font.canDisplay('a')) { + FontData fontData = toSwtFontData(font, 10); + org.eclipse.swt.graphics.Font swtFont = new org.eclipse.swt.graphics.Font(item.getDisplay(), fontData); + item.setFont(swtFont); + } + } + }); + + + // Updates selected font to font name text + fontFamilyTable.addSelectionListener(new SelectionListener() { + + @Override + public void widgetSelected(SelectionEvent e) { + TableItem[] selection = fontFamilyTable.getSelection(); + + if(selection.length > 0) { + String family = selection[0].getText(); + fontName.setText(family); + fontChanged(); + } + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + } + }); + + /* + * Forces focus to font name texts and starts editing it, + * if user presses a letter when focus is in font family table + */ + fontFamilyTable.addKeyListener(new KeyListener() { + @Override + public void keyReleased(KeyEvent e) { + } + + @Override + public void keyPressed(KeyEvent e) { + if(Character.isLetter(e.character)) { + fontName.forceFocus(); + fontName.setTextChars(new char[] {e.character}); + fontName.setSelection(1, 1); + } + } + }); + } + + /** + * Implements interactions between font name text and font family table, when + * the text in font name text has been changed. + * @param name New name + * @param autoComplete Has autocomplete been called + */ + protected void fontFamilyTextModified(String name, boolean autoComplete) { + if(name.isEmpty()) + return; + for(int i = 0; i < familyIndex.size(); i++) { + String family = familyIndex.get(i); + if(family.equals(name)) { + //complete match + fontFamilyTable.select(i); + selectFontFamily(name); + break; + } else if(family.toLowerCase().equals(name.toLowerCase())) { + // Wrong case but correct name + fontName.setText(family); + fontName.setSelection(family.length(), family.length()); + fontFamilyTable.setTopIndex(i); + fontChanged(); + break; + } else if(family.toLowerCase().startsWith(name.toLowerCase())) { + if(autoComplete) { + // Fill in the rest of the name + fontName.setText(family); + fontName.setSelection(family.length()); + fontChanged(); + // The beginning is correct, help user by displaying the nearest name + } else { + fontFamilyTable.setTopIndex(i); + } + break; + } + } + } + + + /** + * Adds listeners for font style text and table + */ + protected void addFontStyleListeners() { + + // Font style modify listener + fontStyle.addModifyListener(new ModifyListener() { + + @Override + public void modifyText(ModifyEvent e) { + Text text = (Text) e.widget; + String name = text.getText(); + fontStyleTextModified(name, false); + } + }); + + // Font style key listener for auto-complete + fontStyle.addKeyListener(new KeyListener() { + + @Override + public void keyReleased(KeyEvent e) { + } + + @Override + public void keyPressed(KeyEvent e) { + if ((e.character == ' ') && ((e.stateMask & SWT.CTRL) != 0) ) { + fontStyleTextModified(fontStyle.getText(), true); + e.doit = false; + } else if(e.keyCode == SWT.CR || e.keyCode == SWT.LF || e.keyCode == SWT.KEYPAD_CR) { + fontChanged(); + } + } + }); + + // Update selected style to font style text + fontStyleTable.addSelectionListener(new SelectionListener() { + + @Override + public void widgetSelected(SelectionEvent e) { + TableItem[] selection = fontStyleTable.getSelection(); + + if(selection.length > 0) { + String family = selection[0].getText(); + fontStyle.setText(family); + fontChanged(); + } + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + } + }); + + /* + * Forces focus to font style text and starts editing it, + * if user presses a letter when focus is in font style table + */ + fontStyleTable.addKeyListener(new KeyListener() { + @Override + public void keyReleased(KeyEvent e) { + } + + @Override + public void keyPressed(KeyEvent e) { + if(Character.isLetter(e.character)) { + fontStyle.forceFocus(); + fontStyle.setTextChars(new char[] {e.character}); + fontStyle.setSelection(1, 1); + e.doit = false; + } else if(e.keyCode == SWT.CR || e.keyCode == SWT.LF || e.keyCode == SWT.KEYPAD_CR) { + fontChanged(); + } + } + }); + } + + /** + * Handles interactions between font style text and font style table, when + * font style text has been changed + * + * @param name New text for font style + * @param autoComplete is auto-completion used + */ + protected void fontStyleTextModified(String name, boolean autoComplete) { + if(name.isEmpty()) + return; + + for(int i = 0; i < fontStyleTable.getItemCount(); i++) { + TableItem item = fontStyleTable.getItem(i); + String style = item.getText(); + + if(style.equals(name)) { + //complete match + fontStyleTable.select(i); + break; + } else if(style.toLowerCase().equals(name.toLowerCase())) { + // Wrong case, correct style. -> fix the case + fontStyle.setText(style); + fontStyle.setSelection(style.length()); + fontStyleTable.setTopIndex(i); + fontChanged(); + break; + } else if(style.toLowerCase().startsWith(name.toLowerCase())) { + // The beginning of the word is correct + fontStyleTable.setTopIndex(i); + if(autoComplete) { + // Fill in the rest of the name + fontStyle.setText(style); + fontStyle.setSelection(style.length()); + fontChanged(); + // The beginning is correct, help user by displaying the nearest name + } else { + fontStyleTable.setTopIndex(i); + } + break; + } + } + } + + + /** + * Listeners for font size text and font size table + */ + protected void addFontSizeListeners() { + + // Select an item from size table, if there is a matching item + fontSize.addKeyListener(new KeyListener() { + + @Override + public void keyReleased(KeyEvent e) { + Text text = (Text) e.widget; + String size = text.getText(); + + for(int i = 0; i < sizes.length; i++) { + if(sizes[i].equals(size)) { + fontSizeTable.select(i); + fontSizeTable.setTopIndex(i); + fontChanged(); + break; + } + } + } + + @Override + public void keyPressed(KeyEvent e) { + } + }); + + // Change the text in size text according to the selection in size table + fontSizeTable.addSelectionListener(new SelectionListener() { + + @Override + public void widgetSelected(SelectionEvent e) { + fontSize.setText(fontSizeTable.getSelection()[0].getText()); + fontChanged(); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + } + }); + + // Change focus from table to text, if user starts to write a number + fontSizeTable.addKeyListener(new KeyListener() { + + @Override + public void keyReleased(KeyEvent e) { } + + @Override + public void keyPressed(KeyEvent e) { + if(Character.isDigit(e.character)) { + fontSize.setTextChars(new char[]{e.character}); + fontSize.setSelection(1); + fontSize.forceFocus(); + e.doit = false; + } + } + }); + + } + + /** + * Creates the contents of fontStyleTable according to the selected font family + * @param family Selected font family + */ + protected void selectFontFamily(String family) { + String old = fontStyle.getText(); + String selection = null, optionalSelection = null; + + // Clear the table + fontStyleTable.removeAll(); + + if(familyIndex.indexOf(family) > -1) { + for(int i = 0; i < fonts.get(family).size(); i++) { + Font font = fonts.get(family).get(i); + + String name = font.getFontName(Locale.ROOT); + + // Style is "Regular", unless otherwise defined + String style = "Regular"; + if(name.length() > family.length()) + style = name.substring(family.length() + 1); + + // If previous font was bold, try to conserve the style + if(old.equals(style)) + selection = style; + else if((!old.isEmpty() && style.contains(old)) || optionalSelection == null) + optionalSelection = style; + + TableItem item = new TableItem (fontStyleTable, SWT.NONE); + item.setText (0, style); + item.setData(font); + + // If the font is not symbolic, use the font in the created item + if(font.canDisplay('a')) { + FontData fontData = toSwtFontData(font, 10); + org.eclipse.swt.graphics.Font swtFont = new org.eclipse.swt.graphics.Font(item.getDisplay(), fontData); + item.setFont(swtFont); + } + } + fontStyleTable.setItemCount(fonts.get(family).size()); + + if(selection == null) + selection = optionalSelection; + + fontStyle.setText(selection); + fontStyle.setSelection(selection.length(), selection.length()); + + setFontStyleTableWidth(); + } + } + + /** + * Set width for style table column. Width can change, if scroll bar appears + */ + protected void setFontStyleTableWidth() { + Rectangle area = fontStyleTable.getClientArea(); + Point size = fontStyleTable.computeSize(SWT.DEFAULT, SWT.DEFAULT); + ScrollBar vBar = fontStyleTable.getVerticalBar(); + int width = 100; + if (area.height == 0 || size.y <= area.height) { + Point vBarSize = vBar.getSize(); + width += vBarSize.x; + } + fontStyleTable.getColumn(0).setWidth(width); + } + + + /** + * Builds SWT FontData from AWT font. Simple conversion. + * + * @param font AWT font + * @param height Height for the created data (or -1 if inherited directly from awt font, size matching not guaranteed) + * @return + */ + protected static FontData toSwtFontData(Font font, int height) { + FontData fontData = new FontData(); + fontData.setName(font.getFontName()); + fontData.setStyle(font.getStyle()); + fontData.setHeight(height > 0 ? height : font.getSize()); + return fontData; + } + + + public void addFontModifiedListener(FontChangeListener listener) { + modifyListeners.add(listener); + } + + public void removeFontModifiedListener(FontChangeListener listener) { + modifyListeners.remove(listener); + } + + public List getFontModifiedListener() { + ArrayList listeners = new ArrayList(modifyListeners.size()); + for(Object l : modifyListeners.getListeners()) + listeners.add((FontChangeListener)l); + return listeners; + } + + /** + * Called when some property of the font definiton has changed. + * Calls font change listeners. + */ + protected void fontChanged() { + Font font = getAWTFont(); + if(font != null) { + + int style = 0; + style |= (font.getFontName(Locale.ROOT).contains("Bold") ? SWT.BOLD : 0); + style |= (font.getFontName(Locale.ROOT).contains("Italic") ? SWT.ITALIC : 0); + FontData data = new FontData(font.getFamily(Locale.ROOT), font.getSize(), style); + org.eclipse.swt.graphics.Font swt = new org.eclipse.swt.graphics.Font(this.getDisplay(), data); + + Object[] listenersArray = modifyListeners.getListeners(); + for (int i = 0; i < listenersArray.length; i++) { + ((FontChangeListener)listenersArray[i]).awtFontChanged(font); + ((FontChangeListener)listenersArray[i]).swtFontChanged(swt); + } + } + } + + + + /** + * Font change listening interface + */ + public interface FontChangeListener { + public void awtFontChanged(Font font); + public void swtFontChanged(org.eclipse.swt.graphics.Font font); + } +}