/******************************************************************************* * Copyright (c) Emil Crumhorn - Hexapixel.com - emil.crumhorn@gmail.com * 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: * emil.crumhorn@gmail.com - initial API and implementation *******************************************************************************/ package org.eclipse.nebula.widgets.calendarcombo; import java.text.DateFormatSymbols; import java.util.ArrayList; import java.util.Calendar; import java.util.Locale; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.MouseListener; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.graphics.Font; 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.Event; import org.eclipse.swt.widgets.Listener; class MonthPick extends Canvas implements MouseListener { // months to show in the popup private int mMonthsToShow = 7; private Calendar mStart; private Rectangle mBounds; private int mTopDateSpacer = 2; private int mInnerWidth = 99; private DateFormatSymbols mDFS; private String[] mMonths; private ArrayList mMonthEntries = new ArrayList(); private MonthEntry mHoverEntry; private boolean mEnableDoubleBuffering = true; private boolean mCreated; private Thread mAboveThread = null; private Thread mBelowThread = null; private boolean mAboveRun = false; private boolean mBelowRun = false; private CalendarComposite mCalendarComposite = null; private Calendar mSelectedMonth = null; private Locale mLocale; private ISettings mSettings; private Listener mDisposeListener; private int sleepTime = 200; private Listener mCarbonMouseMoveListener; private Listener mCarbonMouseUpListener; public MonthPick(Composite parent, int style, Calendar start, CalendarComposite cc, ISettings settings, Locale locale) { super(parent, style | SWT.NO_BACKGROUND | SWT.NO_REDRAW_RESIZE | SWT.ON_TOP); mDFS = new DateFormatSymbols(locale); mMonths = mDFS.getMonths(); mStart = start; mCalendarComposite = cc; mLocale = locale; mSettings = settings; setSize(101, 112); setCapture(true); addMouseListener(this); // oh carbon, why art thou so different! // basically we need to do everything as far as listeners go on Carbon, because everything is fired differently if (CalendarCombo.OS_CARBON) { mCarbonMouseMoveListener = new Listener() { public void handleEvent(Event event) { // widget mouse movement reporting is really off on Carbon, check if we're actually reporting a mouse move on us if (Display.getDefault().getCursorControl() == MonthPick.this) { mouseMove(event.x, event.y); } } }; mCarbonMouseUpListener = new Listener() { public void handleEvent(Event event) { mBelowRun = false; mAboveRun = false; mouseUp(null); } }; Display.getDefault().addFilter(SWT.MouseMove, mCarbonMouseMoveListener); Display.getDefault().addFilter(SWT.MouseUp, mCarbonMouseUpListener); addListener(SWT.MouseExit, new Listener() { public void handleEvent(Event event) { // if mouse exited east or west, ignore if (event.x < 0 || event.x > getSize().x) return; // mous exit top or bottom, run thread if (event.y < 0) { mBelowRun = false; runAbove(); } else { mAboveRun = false; runBelow(); } } }); addListener(SWT.MouseEnter, new Listener() { public void handleEvent(Event event) { // halt threads mAboveRun = false; mBelowRun = false; } }); } else { addListener(SWT.MouseMove, new Listener() { public void handleEvent(Event event) { mouseMove(event.x, event.y); } }); } addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { setCapture(false); if (CalendarCombo.OS_CARBON) { Display.getDefault().removeFilter(SWT.MouseMove, mCarbonMouseMoveListener); Display.getDefault().removeFilter(SWT.MouseUp, mCarbonMouseUpListener); } } }); addPaintListener(new PaintListener() { public void paintControl(PaintEvent event) { paint(event); } }); // on carbon the shell doesn't die if it's stuck open and the mouse is // clicked out of bounds, so we kill it on mouse down (month has time to // be set prior) if (CalendarCombo.OS_CARBON) { mDisposeListener = new Listener() { public void handleEvent(Event e) { Display.getDefault().removeFilter(SWT.MouseDown, mDisposeListener); dispose(); } }; Display.getDefault().addFilter(SWT.MouseDown, mDisposeListener); } } private void paint(PaintEvent event) { GC gc = event.gc; // fonts are disposed when calendar is disposed Font used = null; if (CalendarCombo.OS_CARBON) { used = mSettings.getCarbonDrawFont(); if (used != null) gc.setFont(used); } else if (CalendarCombo.OS_WINDOWS) { used = mSettings.getWindowsMonthPopupDrawFont(); if (used != null) gc.setFont(used); } // double buffering. this could be triple buffering if the platform does // it automatically, windows XP does not seem to however // basically, we draw all the updates onto an Image in memory, then we // transfer the contents thereof onto the canvas. // that way there is 0 flicker, which is the desired effect. if (mCreated && mEnableDoubleBuffering) { try { Image buffer = new Image(Display.getDefault(), super .getBounds()); GC gc2 = new GC(buffer); drawOntoGC(gc2); // transfer the image buffer onto this canvas // just drawImage(buffer, w, h) didn't work, so we do the whole // source transfer call Rectangle b = getBounds(); gc.drawImage(buffer, 0, 0, b.width, b.height, 0, 0, b.width, b.height); // dispose the buffer, very important or we'll run out of // address space for buffered images buffer.dispose(); gc2.dispose(); } catch (IllegalArgumentException iea) { // seems to come here for some reason when we switch phases // while the gantt chart is being viewed, I'm not sure why // but no time to figure it out for the demo.. so instead of // buffering, just draw it onto the GC drawOntoGC(gc); } } else { drawOntoGC(gc); mCreated = true; } // don't dispose font, they are disposed when the CalendarCombo are disposed } private void drawOntoGC(GC gc) { mBounds = super.getBounds(); drawBG(gc); drawDates(gc); drawBorder(gc); } private void drawBG(GC gc) { gc.setForeground(ColorCache.getBlack()); gc.setBackground(ColorCache.getWhite()); gc.fillRectangle(mBounds.x - 50, mBounds.y, mBounds.width + 50, mBounds.height); } private void drawBorder(GC gc) { Rectangle bounds = getClientArea(); gc.setForeground(ColorCache.getBlack()); gc.drawRectangle(bounds.x, bounds.y, bounds.width - 1, bounds.height - 1); } private void drawDates(GC gc) { int y = mTopDateSpacer; int spacer = 2; Calendar temp = Calendar.getInstance(mLocale); temp.setTime(mStart.getTime()); temp.add(Calendar.MONTH, -3); if (!CalendarCombo.OS_CARBON) { gc.setFont(mSettings.getWindowsMonthPopupDrawFont()); } else gc.setFont(mSettings.getCarbonDrawFont()); gc.setForeground(ColorCache.getBlack()); mMonthEntries.clear(); // gc.setBackground(ColorCache.getWhite()); // gc.fillRectangle(bounds); for (int i = 0; i < mMonthsToShow; i++) { int mo = temp.get(Calendar.MONTH); String toDraw = mMonths[mo] + " " + temp.get(Calendar.YEAR); Point p = gc.stringExtent(toDraw); // center the text in the available space int rest = mInnerWidth - p.x; rest /= 2; Rectangle rect = new Rectangle(0, y - 1, mBounds.width, p.y + 1); if (mHoverEntry != null) { // draw hover entry if (mHoverEntry.getRect().equals(rect)) { gc.setBackground(ColorCache.getBlack()); gc.fillRectangle(mHoverEntry.getRect()); gc.setBackground(ColorCache.getWhite()); gc.setForeground(ColorCache.getWhite()); // lastHoverRect = hoverEntry.getRect(); mSelectedMonth = mHoverEntry.getCalendar(); } } else { // draw today if (i == 3) { gc.setBackground(ColorCache.getBlack()); gc.fillRectangle(rect); gc.setBackground(ColorCache.getWhite()); gc.setForeground(ColorCache.getWhite()); } } gc.drawString(toDraw, rest, y, true); gc.setForeground(ColorCache.getBlack()); MonthEntry me = new MonthEntry(temp); me.setRect(rect); me.setyPos(y); me.setText(toDraw); mMonthEntries.add(me); temp.add(Calendar.MONTH, 1); y += p.y + spacer; } } public void mouseDoubleClick(MouseEvent event) { } public void mouseDown(MouseEvent event) { } public void mouseUp(MouseEvent event) { mCalendarComposite.mouseUp(event); if (mSelectedMonth != null) mCalendarComposite.setDate(mSelectedMonth); this.dispose(); } public void mouseMove(final int x, final int y) { for (int i = 0; i < mMonthEntries.size(); i++) { MonthEntry mEntry = (MonthEntry) mMonthEntries.get(i); if (isInside(x, y, mEntry.getRect())) { mHoverEntry = mEntry; mAboveRun = false; mBelowRun = false; redraw(); return; } } mHoverEntry = null; if (!CalendarCombo.OS_CARBON) { if (y < getLocation().y) { runAbove(); } else { runBelow(); } } } private void runAbove() { if (mAboveThread != null && mAboveThread.isAlive() && mAboveRun) { return; } mBelowRun = false; mAboveThread = new Thread() { public void run() { while (mAboveRun) { try { sleep(sleepTime); } catch (InterruptedException e) { e.printStackTrace(); } Display.getDefault().asyncExec(new Runnable() { public void run() { if (isDisposed()) mAboveThread = null; else scrollOneMonth(true); } }); } } }; mAboveRun = true; mAboveThread.start(); } private void runBelow() { if (mBelowThread != null && mBelowThread.isAlive() && mBelowRun) { return; } mBelowRun = false; mBelowThread = new Thread() { public void run() { while (mBelowRun) { try { sleep(sleepTime); } catch (InterruptedException e) { e.printStackTrace(); } Display.getDefault().asyncExec(new Runnable() { public void run() { if (isDisposed()) mBelowThread = null; else scrollOneMonth(false); } }); } } }; mBelowRun = true; mBelowThread.start(); } private void scrollOneMonth(boolean up) { if (up) { mStart.add(Calendar.MONTH, -1); redraw(); } else { mStart.add(Calendar.MONTH, 1); redraw(); } } private boolean isInside(int x, int y, Rectangle rect) { if (rect == null) { return false; } if (x >= rect.x && y >= rect.y && x <= (rect.x + rect.width) && y <= (rect.y + rect.height)) { return true; } return false; } class MonthEntry { private Calendar mCal; private Rectangle mRect; private int mYPos; private String text; public MonthEntry(Calendar cal) { this.mCal = (Calendar) cal.clone(); } public Calendar getCalendar() { return mCal; } public Rectangle getRect() { return mRect; } public void setRect(Rectangle rect) { this.mRect = rect; } public int getyPos() { return mYPos; } public void setyPos(int yPos) { this.mYPos = yPos; } public String getText() { return text; } public void setText(String text) { this.text = text; } } }