/*! ****************************************************************************** * * Pentaho Data Integration * * Copyright (C) 2002-2013 by Pentaho : http://www.pentaho.com * ******************************************************************************* * * 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 org.pentaho.di.ui.core.widget; import java.util.List; import java.util.Vector; import org.eclipse.jface.wizard.WizardPage; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseMoveListener; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.ScrollBar; import org.pentaho.di.core.gui.TextFileInputFieldInterface; import org.pentaho.di.ui.core.PropsUI; import org.pentaho.di.ui.core.gui.GUIResource; /** * Widget to draw the character of a fixed length text-file in a graphical way. * * @author Matt * @since 17-04-2004 */ public class TableDraw extends Canvas { private Display display; private Color bg; // private Font font; private Color black, red, blue, lgray; private Point offset; private ScrollBar hori; private ScrollBar vert; private Image dummy_image; private GC dummy_gc; private int maxlen; private int fontheight; private int fontwidth; private Image cache_image; private int prev_fromx, prev_tox, prev_fromy, prev_toy; private Vector<TextFileInputFieldInterface> fields; private List<String> rows; private static final int LEFT = 50; private static final int TOP = 30; private static final int MARGIN = 10; private int potential_click; private WizardPage wPage; private String prevfieldname; public TableDraw( Composite parent, PropsUI props, WizardPage wPage, Vector<TextFileInputFieldInterface> fields ) { super( parent, SWT.NO_BACKGROUND | SWT.H_SCROLL | SWT.V_SCROLL ); this.wPage = wPage; this.fields = fields; prevfieldname = ""; potential_click = -1; // Cache displayed text... cache_image = null; prev_fromx = -1; prev_tox = -1; prev_fromy = -1; prev_toy = -1; display = parent.getDisplay(); bg = GUIResource.getInstance().getColorBackground(); // font = GUIResource.getInstance().getFontGrid(); fontheight = props.getGridFont().getHeight(); black = GUIResource.getInstance().getColorBlack(); red = GUIResource.getInstance().getColorRed(); blue = GUIResource.getInstance().getColorBlue(); lgray = GUIResource.getInstance().getColorLightGray(); hori = getHorizontalBar(); vert = getVerticalBar(); // Determine font width... dummy_image = new Image( display, 1, 1 ); dummy_gc = new GC( dummy_image ); // dummy_gc.setFont(font); String teststring = "ABCDEF"; fontwidth = Math.round( dummy_gc.textExtent( teststring ).x / teststring.length() ); setBackground( bg ); // setFont(font); addPaintListener( new PaintListener() { public void paintControl( PaintEvent e ) { TableDraw.this.paintControl( e ); } } ); addDisposeListener( new DisposeListener() { public void widgetDisposed( DisposeEvent arg0 ) { dummy_gc.dispose(); dummy_image.dispose(); if ( cache_image != null ) { cache_image.dispose(); } } } ); hori.addSelectionListener( new SelectionAdapter() { public void widgetSelected( SelectionEvent e ) { redraw(); } } ); vert.addSelectionListener( new SelectionAdapter() { public void widgetSelected( SelectionEvent e ) { redraw(); } } ); hori.setThumb( 100 ); vert.setThumb( 100 ); // Mouse events! addMouseListener( new MouseAdapter() { public void mouseDown( MouseEvent e ) { Point offset = getOffset(); int posx = (int) Math.round( (double) ( e.x - LEFT - MARGIN - offset.x ) / ( (double) fontwidth ) ); if ( posx > 0 ) { potential_click = posx; redraw(); } } public void mouseUp( MouseEvent e ) { if ( potential_click > 0 ) { setMarker( potential_click ); // clear or set! potential_click = -1; redraw(); } } } ); addMouseMoveListener( new MouseMoveListener() { public void mouseMove( MouseEvent e ) { int posx = (int) Math.round( (double) ( e.x - LEFT - MARGIN - offset.x ) / ( (double) fontwidth ) ); // Clicked and mouse is down: move marker to a new location... if ( potential_click >= 0 ) { if ( posx > 0 ) { potential_click = posx; redraw(); } } TextFileInputFieldInterface field = getFieldOnPosition( posx ); if ( field != null && !field.getName().equalsIgnoreCase( prevfieldname ) ) { setToolTipText( field.getName() + " : length=" + field.getLength() ); prevfieldname = field.getName(); } } } ); } private TextFileInputFieldInterface getFieldOnPosition( int x ) { for ( int i = 0; i < fields.size(); i++ ) { TextFileInputFieldInterface field = fields.get( i ); int pos = field.getPosition(); int len = field.getLength(); if ( pos <= x && pos + len > x ) { return field; } } return null; } private void setMarker( int x ) { int idx = -1; int highest_smaller = -1; for ( int i = 0; i < fields.size(); i++ ) { TextFileInputFieldInterface field = fields.get( i ); int pos = field.getPosition(); int len = field.getLength(); if ( pos == potential_click ) { idx = i; } if ( highest_smaller < 0 && pos + len >= x ) { highest_smaller = i; // The first time this occurs is OK. } } // OK, so we need to add a new field on the location of the previous one // Actually, we make the previous field shorter // // Field 1: pos=0, length=100 // // becomes // // Field1: pos=0, length=50 // Field2: pos=51, length=50 // // We know the number of the new field : lowest_larger. // // Note: We should always have one field @ position 0, length max // if ( idx < 0 ) { // Position is not yet in the list, the field is not deleted, but added if ( highest_smaller >= 0 ) { // OK, let's add a new field, but split the length of the previous field // We want to keep this list sorted, so add at position lowest_larger. // We want to change the previous entry and add another after it. TextFileInputFieldInterface prevfield = fields.get( highest_smaller ); int newlength = prevfield.getLength() - ( x - prevfield.getPosition() ); TextFileInputFieldInterface field = prevfield.createNewInstance( getNewFieldname(), x, newlength ); fields.add( highest_smaller + 1, field ); // Don't forget to make the previous field shorter prevfield.setLength( x - prevfield.getPosition() ); } } else { if ( highest_smaller >= 0 ) { // Now we need to remove the field with the same starting position // The previous field need to receive extra length TextFileInputFieldInterface prevfield = fields.get( highest_smaller ); TextFileInputFieldInterface field = fields.get( idx ); prevfield.setLength( prevfield.getLength() + field.getLength() ); // Remove the field fields.remove( idx ); } } // Something has changed: change wizard page... wPage.setPageComplete( wPage.canFlipToNextPage() ); } // Loop from 1 to ... and see if we have an empty Fieldnr available. private String getNewFieldname() { int nr = 1; String name = "Field" + nr; while ( fieldExists( name ) ) { nr++; name = "Field" + nr; } return name; } private boolean fieldExists( String name ) { for ( int i = 0; i < fields.size(); i++ ) { TextFileInputFieldInterface field = fields.get( i ); if ( name.equalsIgnoreCase( field.getName() ) ) { return true; } } return false; } public void setRows( List<String> rows ) { this.rows = rows; maxlen = getMaxLength(); redraw(); } // Draw the widget. public void paintControl( PaintEvent e ) { offset = getOffset(); if ( offset == null ) { return; } Point area = getArea(); Point max = getMaximum(); Point thumb = getThumb( area, max ); hori.setThumb( thumb.x ); vert.setThumb( thumb.y ); // From where do we need to draw? int fromy = -offset.y / ( fontheight + 2 ); int toy = fromy + ( area.y / ( fontheight + 2 ) ); int fromx = -offset.x / fontwidth; int tox = fromx + ( area.x / fontwidth ); Image image = new Image( display, area.x, area.y ); if ( fromx != prev_fromx || fromy != prev_fromy || tox != prev_tox || toy != prev_toy ) { if ( cache_image != null ) { cache_image.dispose(); cache_image = null; } cache_image = new Image( display, area.x, area.y ); GC gc = new GC( cache_image ); // We have a cached image: draw onto it! int linepos = TOP - 5; gc.setBackground( bg ); gc.fillRectangle( LEFT, TOP, area.x, area.y ); // We draw in black... gc.setForeground( black ); // gc.setFont(font); // Draw the text // for ( int i = fromy; i < rows.size() && i < toy; i++ ) { String str = rows.get( i ); for ( int p = fromx; p < str.length() && p < tox; p++ ) { gc.drawText( "" + str.charAt( p ), LEFT + MARGIN + p * fontwidth + offset.x, TOP + i * ( fontheight + 2 ) + offset.y, true ); } if ( str.length() < tox ) { gc.setForeground( red ); gc.setBackground( red ); int x_oval = LEFT + MARGIN + str.length() * fontwidth + offset.x; int y_oval = TOP + i * ( fontheight + 2 ) + offset.y; gc.drawOval( x_oval, y_oval, fontwidth, fontheight ); gc.fillOval( x_oval, y_oval, fontwidth, fontheight ); gc.setForeground( black ); gc.setBackground( bg ); } } // Draw the rulers // HORIZONTAL gc.setBackground( lgray ); gc.fillRectangle( LEFT + MARGIN, 0, area.x, linepos + 1 ); gc.setBackground( bg ); gc.drawLine( LEFT + MARGIN, linepos, area.x, linepos ); // Little tabs, small ones, every 5 big one, every 10 the number above... for ( int i = fromx; i < maxlen + 10 && i < tox + 10; i++ ) { String number = "" + i; int numsize = number.length() * fontwidth; if ( i > 0 && ( i % 10 ) == 0 ) { gc.drawText( "" + i, LEFT + MARGIN + i * fontwidth - numsize / 2 + offset.x, linepos - 10 - fontheight, true ); } if ( i > 0 && ( i % 5 ) == 0 ) { gc.drawLine( LEFT + MARGIN + i * fontwidth + offset.x, linepos, LEFT + MARGIN + i * fontwidth + offset.x, linepos - 5 ); } else { gc.drawLine( LEFT + MARGIN + i * fontwidth + offset.x, linepos, LEFT + MARGIN + i * fontwidth + offset.x, linepos - 3 ); } } // VERTICIAL gc.setBackground( lgray ); gc.fillRectangle( 0, TOP, LEFT, area.y ); gc.drawLine( LEFT, TOP, LEFT, area.y ); for ( int i = fromy; i < rows.size() && i < toy; i++ ) { String number = "" + ( i + 1 ); int numsize = number.length() * fontwidth; gc.drawText( number, LEFT - 5 - numsize, TOP + i * ( fontheight + 2 ) + offset.y, true ); gc.drawLine( LEFT, TOP + ( i + 1 ) * ( fontheight + 2 ) + offset.y, LEFT - 5, TOP + ( i + 1 ) * ( fontheight + 2 ) + offset.y ); } gc.dispose(); } GC gc = new GC( image ); // Draw the cached image onto the canvas image: gc.drawImage( cache_image, 0, 0 ); // Also draw the markers... gc.setForeground( red ); gc.setBackground( red ); for ( int i = 0; i < fields.size(); i++ ) { int x = ( fields.get( i ) ).getPosition(); if ( x >= fromx && x <= tox ) { drawMarker( gc, x, area.y ); } } if ( potential_click >= 0 ) { gc.setForeground( blue ); gc.setBackground( blue ); drawMarker( gc, potential_click, area.y ); } // Draw the image: e.gc.drawImage( image, 0, 0 ); gc.dispose(); image.dispose(); } private void drawMarker( GC gc, int x, int maxy ) { int[] triangle = new int[] { LEFT + MARGIN + x * fontwidth + offset.x, TOP - 4, LEFT + MARGIN + x * fontwidth + offset.x + 3, TOP + 1, LEFT + MARGIN + x * fontwidth + offset.x - 3, TOP + 1 }; gc.fillPolygon( triangle ); gc.drawPolygon( triangle ); gc .drawLine( LEFT + MARGIN + x * fontwidth + offset.x, TOP + 1, LEFT + MARGIN + x * fontwidth + offset.x, maxy ); } private Point getOffset() { Point area = getArea(); Point max = getMaximum(); Point thumb = getThumb( area, max ); Point offset = getOffset( thumb, area ); return offset; } private Point getThumb( Point area, Point max ) { Point thumb = new Point( 0, 0 ); if ( max.x <= area.x ) { thumb.x = 100; } else { thumb.x = Math.round( 100 * area.x / max.x ); } if ( max.y <= area.y ) { thumb.y = 100; } else { thumb.y = Math.round( 100 * area.y / max.y ); } return thumb; } private Point getOffset( Point thumb, Point area ) { Point p = new Point( 0, 0 ); Point sel = new Point( hori.getSelection(), vert.getSelection() ); if ( thumb.x == 0 || thumb.y == 0 ) { return p; } p.x = Math.round( -sel.x * area.x / thumb.x ); p.y = Math.round( -sel.y * area.y / thumb.y ); return p; } private Point getMaximum() { int maxx = 0; int maxy = ( rows.size() + 10 ) * ( fontheight + 2 ); for ( int i = 0; i < rows.size(); i++ ) { String str = rows.get( i ); int len = ( str.length() + 10 ) * fontwidth; if ( maxx < len ) { maxx = len; } } return new Point( maxx, maxy ); } private int getMaxLength() { int maxx = 0; for ( int i = 0; i < rows.size(); i++ ) { String str = rows.get( i ); int len = str.length(); if ( maxx < len ) { maxx = len; } } return maxx; } private Point getArea() { Rectangle rect = getClientArea(); Point area = new Point( rect.width, rect.height ); return area; } public Vector<TextFileInputFieldInterface> getFields() { return fields; } public void setFields( Vector<TextFileInputFieldInterface> fields ) { this.fields = fields; } public void clearFields() { fields = new Vector<TextFileInputFieldInterface>(); } }