/* * Copyright 2008 University of Prince Edward Island * * 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 ca.upei.ic.timetable.client; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.webinit.gwt.client.Observable; import org.webinit.gwt.client.Observer; import ca.upei.ic.timetable.client.CalendarItem.Day; import ca.upei.ic.timetable.client.CalendarItem.TimeInterval; import com.google.gwt.core.client.GWT; import com.google.gwt.user.client.ui.AbsolutePanel; import com.google.gwt.user.client.ui.ClickListener; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.HorizontalPanel; import com.google.gwt.user.client.ui.ScrollPanel; import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; /** * GWT Calendar Widget * * @author felix */ public abstract class CalendarPanel extends Composite implements Observer, Observable { private Calendar calendar_; private ScrollPanel outerPanel_; private HorizontalPanel panel_; private Map<CalendarItem, Set<Widget>> itemWidgets_; private int courseWidth_; private int calendarInnerHeight_; private int calendarInnerWidth_; private int calendarLeftDescriptionWidth_ = 60; private int width_, height_; private CellClickListener cellClickListener_; public CalendarPanel() { } public void setCellClickListener(CellClickListener listener) { cellClickListener_ = listener; } /** * Set the Calendar model * * @param calendar */ public void setCalendar(Calendar calendar) { calendar_ = calendar; } /** * Set the pixel size */ @Override public void setPixelSize(int width, int height) { this.width_ = width; this.height_ = height; } /** * This method must be called after all settings done. */ void init() { calendarInnerHeight_ = Calendar.RESOLUTION * 14 * 4; calendarInnerWidth_ = width_ - 17; // create the horizontalPanel panel_ = GWT.create(HorizontalPanel.class); // add panels for (int i = 0; i < 7; i++) { panel_.add((Widget) GWT.create(AbsolutePanel.class)); } // create the scroll panel outerPanel_ = GWT.create(ScrollPanel.class); // create the absolute wrapper AbsolutePanel wrapper = GWT.create(AbsolutePanel.class); wrapper.setPixelSize(calendarInnerWidth_, calendarInnerHeight_); // set the calendar width and height panel_.setPixelSize( calendarInnerWidth_ - calendarLeftDescriptionWidth_, calendarInnerHeight_); // add the sub panel and set size outerPanel_.add(wrapper); outerPanel_.setPixelSize(width_, height_); // deal with type switch (calendar_.getType()) { case Calendar.FIVE: panel_.getWidget(0).setSize("0px", "0px"); courseWidth_ = (calendarInnerWidth_ - calendarLeftDescriptionWidth_) / 5; // minus // the // scroll // bar // width for (int i = 1; i < 6; i++) { panel_.getWidget(i).setPixelSize(courseWidth_, calendarInnerHeight_); } panel_.getWidget(6).setSize("0px", "0px"); break; case Calendar.SEVEN: courseWidth_ = (calendarInnerWidth_ - calendarLeftDescriptionWidth_) / 7; for (int i = 0; i < 7; i++) { panel_.getWidget(i).setPixelSize(courseWidth_, calendarInnerHeight_); } break; default: throw new IllegalArgumentException("Calendar type is invalid."); } // add to observer list calendar_.addObserver("itemDidAdd", this); calendar_.addObserver("itemDidRemove", this); // create the grid HorizontalPanel grid = new HorizontalPanel(); grid.setPixelSize(calendarInnerWidth_ - calendarLeftDescriptionWidth_, calendarInnerHeight_); grid.addStyleName("grid"); // FIXME tricky part, needs to change for (int i = 0; i < 5; i++) { VerticalPanel gridColumn = GWT.create(VerticalPanel.class); gridColumn.addStyleName("gridColumn"); gridColumn.setHeight(Integer.toString(calendarInnerHeight_) + "px"); gridColumn.setWidth(Integer.toString(courseWidth_ - 1) + "px"); for (int j = 0; j < 14 * 60 / Calendar.RESOLUTION / 2; j++) { SimplePanel cell = GWT.create(SimplePanel.class); cell.addStyleName("gridCell"); cell.setHeight(Integer.toString(Calendar.RESOLUTION * 2 - 1) + "px"); cell.setWidth(Integer.toString(courseWidth_ - 1) + "px"); final int day = i; final int hour = j; gridColumn.add(PanelUtils.focusPanel(cell, new ClickListener() { public void onClick(Widget sender) { if (cellClickListener_ != null) { Map<String, Integer> params = new HashMap<String,Integer>(); params.put("day", day); params.put("hour", hour); cellClickListener_.setContext(params); cellClickListener_.onClick(sender); } } }, null, null, null)); } grid.add(gridColumn); } // create the with description panel HorizontalPanel panelWithDescription = GWT .create(HorizontalPanel.class); VerticalPanel leftDescription = GWT.create(VerticalPanel.class); leftDescription.setPixelSize(calendarLeftDescriptionWidth_, calendarInnerHeight_); leftDescription.addStyleName("timeColumn"); // first row SimplePanel firstTimeCell = GWT.create(SimplePanel.class); firstTimeCell.addStyleName("timeCell"); firstTimeCell.setPixelSize(calendarLeftDescriptionWidth_ - 1, Calendar.RESOLUTION); leftDescription.add(firstTimeCell); for (int i = 0; i < 14 * 60 / Calendar.RESOLUTION/2; i++) { SimplePanel cell = GWT.create(SimplePanel.class); cell.addStyleName("timeCell"); cell.setPixelSize(calendarLeftDescriptionWidth_ - 1, Calendar.RESOLUTION*2); String half; if (i % 2 == 0) { half = "30"; } else { half = "00"; } // half time adjustment int tp = i / 2 + 8; tp = tp + i % 2; String ampm = "am"; if (tp >= 12) { ampm = "pm"; } if (tp > 12) { tp -= 12; } cell.add(new HTML(Integer.toString(tp) + ":" + half + ampm + " ")); leftDescription.add(cell); } panelWithDescription.add(leftDescription); panelWithDescription.add(panel_); // add the elements wrapper.add(grid, calendarLeftDescriptionWidth_, 0); wrapper.add(panelWithDescription, 0, 0); initWidget(outerPanel_); // set the style primary name outerPanel_.setStylePrimaryName("wi-CalendarPanel"); // create the map itemWidgets_ = new HashMap<CalendarItem, Set<Widget>>(); } /** * The Event Handler */ public boolean observe(String event, Object object, Object arg) { if (event.equals("itemShouldAdd")) { return itemShouldAdd((Calendar) object, (CalendarItem) arg); } else if (event.equals("itemDidAdd")) { itemDidAdd((Calendar) object, (CalendarItem) arg); } else if (event.equals("itemShouldRemove")) { return itemShouldRemove((Calendar) object, (CalendarItem) arg); } else if (event.equals("itemDidRemove")) { itemDidRemove((Calendar) object, (CalendarItem) arg); } return false; } protected boolean itemShouldAdd(Calendar calendar, CalendarItem item) { if (itemWidgets_.containsKey(item)) return false; else return true; } protected boolean itemShouldRemove(Calendar calendar, CalendarItem item) { if (itemWidgets_.containsKey(item)) return true; else return false; } protected void reorganizePanel(AbsolutePanel panel) { Set<Widget> remaining = new HashSet<Widget>(); Set<Widget> processing = new HashSet<Widget>(); // add all widgets to remaining for (Widget w : panel) { remaining.add(w); } // loop until no remaining widgets while (remaining.size() > 0) { // get the first widget Widget first = remaining.iterator().next(); int top = panel.getWidgetTop(first); int bottom = panel.getWidgetTop(first) + first.getOffsetHeight(); remaining.remove(first); processing.add(first); // find out the overlapping widgets from the first one boolean continueProcessing = true; while (continueProcessing) { // default to terminate the processing // this is a hack because no goto statement is supported in // Java. boolean terminateProcessing = true; // iterate all widgets until one overlapping widget is found. for (Widget widget : remaining) { int widgetTop = panel.getWidgetTop(widget); int widgetBottom = panel.getWidgetTop(widget) + widget.getOffsetHeight(); // found if (top < widgetBottom && widgetTop < bottom) { // add to processing set, remove from remaining set processing.add(widget); remaining.remove(widget); // reset top and bottom if necessary if (top > widgetTop) top = widgetTop; if (bottom < widgetBottom) bottom = widgetBottom; // don't terminate processing terminateProcessing = false; break; } } // really want to terminate the process? if (terminateProcessing) continueProcessing = false; } // all processing widgets found, try to reposition/resize the // widgets int count = processing.size(); int index = 0; for (Widget widget : processing) { int widgetWidth = (panel.getOffsetWidth() - 2) / count; int widgetTop = panel.getWidgetTop(widget); int widgetLeft = index * widgetWidth; widget.setWidth(Integer.toString(widgetWidth) + "px"); panel.setWidgetPosition(widget, widgetLeft, widgetTop); index++; } // clear the processing set processing.clear(); } } protected void itemDidAdd(Calendar calendar, CalendarItem item) { // create the widget pool if (!itemWidgets_.containsKey(item)) { itemWidgets_.put(item, new HashSet<Widget>()); } // iterate all entries in the item for (Map.Entry<Integer, Day> entry : item.getDays().entrySet()) { int day = entry.getKey(); // get the parent panel AbsolutePanel parent = (AbsolutePanel) panel_.getWidget(day); // build the widgets Set<TimeInterval> intervals = entry.getValue().getTimeIntervals(); // iterate all time intervals of a day. for (TimeInterval ti : intervals) { // create the widget SimplePanel widget = GWT.create(SimplePanel.class); HTML html = new HTML("<h3>" + item.getTitle() + "</h3><p>" + item.getContent() + "</p>"); widget.setWidget(html); // set the style widget.setStylePrimaryName("wi-CalendarPanel"); widget.addStyleDependentName("selectedCourse"); int begin = 0; if (ti.begin() < 480 / Calendar.RESOLUTION) { // if it's an // afternoon // course begin = ti.begin() + 240 / Calendar.RESOLUTION; } else { // no it's a morning course. begin = ti.begin() - 480 / Calendar.RESOLUTION; } int end; if ((ti.end() <= 540 / Calendar.RESOLUTION) || (begin >= 600 / Calendar.RESOLUTION)) { // it it ends // before // 930 or // begin at // 600pm end = ti.end() + 240 / Calendar.RESOLUTION; } else { end = ti.end() - 480 / Calendar.RESOLUTION; } // set the size int height = (end - begin) * Calendar.RESOLUTION; widget.setPixelSize(courseWidth_ - 2, height - 2); // minus the // border // width parent.add(widget, 0, begin * Calendar.RESOLUTION); // add to widget pool itemWidgets_.get(item).add(widget); } } // reorganize all panels for (int i = 0; i < panel_.getWidgetCount(); i++) { AbsolutePanel panel = (AbsolutePanel) panel_.getWidget(i); reorganizePanel(panel); } } protected void itemDidRemove(Calendar calendar, CalendarItem item) { for (Widget w : itemWidgets_.get(item)) { w.removeFromParent(); } itemWidgets_.remove(item); // reorganize all panels for (int i = 0; i < panel_.getWidgetCount(); i++) { AbsolutePanel panel = (AbsolutePanel) panel_.getWidget(i); reorganizePanel(panel); } } /** * Get Inner height of the Calendar * * @return */ public int getRealHeight() { return calendarInnerHeight_; } }