View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.myfaces.custom.schedule;
21  
22  import java.io.IOException;
23  import java.io.Serializable;
24  import java.text.DateFormat;
25  import java.util.Calendar;
26  import java.util.Date;
27  import java.util.Iterator;
28  import java.util.TimeZone;
29  import java.util.TreeSet;
30  
31  import javax.faces.component.UIComponent;
32  import javax.faces.context.FacesContext;
33  import javax.faces.context.ResponseWriter;
34  
35  import org.apache.commons.logging.Log;
36  import org.apache.commons.logging.LogFactory;
37  import org.apache.myfaces.custom.schedule.model.HalfHourInterval;
38  import org.apache.myfaces.custom.schedule.model.Interval;
39  import org.apache.myfaces.custom.schedule.model.ScheduleDay;
40  import org.apache.myfaces.custom.schedule.model.ScheduleEntry;
41  import org.apache.myfaces.custom.schedule.util.ScheduleUtil;
42  import org.apache.myfaces.shared_tomahawk.renderkit.RendererUtils;
43  import org.apache.myfaces.shared_tomahawk.renderkit.html.HTML;
44  import org.apache.myfaces.shared_tomahawk.renderkit.html.util.FormInfo;
45  
46  /**
47   * <p>
48   * Renderer for the day and workweek views of the Schedule component
49   * </p>
50   * 
51   * @since 1.1.7
52   * @author Jurgen Lust (latest modification by $Author: jlust $)
53   * @author Bruno Aranda (adaptation of Jurgen's code to myfaces)
54   * @version $Revision: 392301 $
55   */
56  public class ScheduleDetailedDayRenderer extends AbstractScheduleRenderer
57          implements Serializable
58  {
59      private static final Log log = LogFactory.getLog(ScheduleDetailedDayRenderer.class);
60      private static final long serialVersionUID = -5103791076091317355L;
61  
62      //~ Instance fields --------------------------------------------------------
63  
64      private final int defaultRowHeightInPixels = 22;
65  
66      //~ Methods ----------------------------------------------------------------
67  
68      /**
69       * @see javax.faces.render.Renderer#encodeBegin(javax.faces.context.FacesContext,
70       *      javax.faces.component.UIComponent)
71       */
72      public void encodeBegin(FacesContext context, UIComponent component)
73              throws IOException
74      {
75          if (!component.isRendered())
76          {
77              return;
78          }
79  
80          super.encodeBegin(context, component);
81  
82          HtmlSchedule schedule = (HtmlSchedule) component;
83          ResponseWriter writer = context.getResponseWriter();
84          int rowHeight = getRowHeight(schedule);
85  
86          //the number of rows in the grid is the number of half hours between
87          //visible start hour and visible end hour, plus 1 for the header
88          int numberOfRows = ((getRenderedEndHour(schedule) - getRenderedStartHour(schedule)) * 2) + 1;
89  
90          //the grid height = 22 pixels times the number of rows + 3, for the
91          //table border and the cellpadding
92          int gridHeight = (numberOfRows * rowHeight) + 3 + 10;
93  
94          //container div for the schedule grid
95          writer.startElement(HTML.DIV_ELEM, schedule);
96          writer.writeAttribute(HTML.CLASS_ATTR, "schedule-detailed-"
97                  + schedule.getTheme(), null);
98          writer.writeAttribute(HTML.STYLE_ATTR, "height: "
99                  + String.valueOf(gridHeight) + "px; overflow: hidden;", null);
100         writeBackgroundStart(context, schedule, writer);
101         writeForegroundStart(context, schedule, writer);
102     }
103 
104     /**
105      * @see javax.faces.render.Renderer#encodeChildren(javax.faces.context.FacesContext,
106      *      javax.faces.component.UIComponent)
107      */
108     public void encodeChildren(FacesContext context, UIComponent component)
109             throws IOException
110     {
111         if (!component.isRendered())
112         {
113             return;
114         }
115 
116         HtmlSchedule schedule = (HtmlSchedule) component;
117         ResponseWriter writer = context.getResponseWriter();
118         String clientId = schedule.getClientId(context);
119         FormInfo parentFormInfo = RendererUtils.findNestingForm(schedule, context);
120         String formId = parentFormInfo == null ? null : parentFormInfo.getFormName();
121 
122         for (Iterator dayIterator = schedule.getModel().iterator(); dayIterator
123                 .hasNext();)
124         {
125             ScheduleDay day = (ScheduleDay) dayIterator.next();
126             String dayBodyId = clientId + "_body_" + ScheduleUtil.getDateId(day.getDate(), schedule.getModel().getTimeZone());
127             writer.startElement(HTML.TD_ELEM, schedule);
128             writer.writeAttribute(HTML.CLASS_ATTR, getStyleClass(schedule,
129                     "column"), null);
130             writer.writeAttribute(HTML.STYLE_ATTR, "height: 100%;", null);
131             writer.startElement(HTML.DIV_ELEM, schedule);
132             writer.writeAttribute(HTML.ID_ATTR, dayBodyId, null);
133             writer.writeAttribute(HTML.CLASS_ATTR, getStyleClass(schedule,
134                     "column"), null);
135             writer.writeAttribute(
136                     HTML.STYLE_ATTR,
137                     "position: relative; top: 0px; left: 0px; width: 100%; height: 100%; z-index: 0;",
138                     null);
139             //register an onclick event listener to a column which will capture
140             //the y coordinate of the mouse, to determine the hour of day
141             if (!schedule.isReadonly() && schedule.isSubmitOnClick()) {
142                 writer.writeAttribute(
143                         HTML.ONMOUSEUP_ATTR,
144                         "fireScheduleTimeClicked(this, event, '"
145                         + formId + "', '"
146                         + clientId
147                         + "');",
148                         null);
149             }
150             writeEntries(context, schedule, day, writer);
151             writer.endElement(HTML.DIV_ELEM);
152             writer.endElement(HTML.TD_ELEM);
153         }
154     }
155 
156     /**
157      * @see javax.faces.render.Renderer#encodeEnd(javax.faces.context.FacesContext,
158      *      javax.faces.component.UIComponent)
159      */
160     public void encodeEnd(FacesContext context, UIComponent component)
161             throws IOException
162     {
163         if (!component.isRendered())
164         {
165             return;
166         }
167 
168         ResponseWriter writer = context.getResponseWriter();
169 
170         writeForegroundEnd(writer);
171         writeBackgroundEnd(writer);
172         writer.endElement(HTML.DIV_ELEM);
173     }
174 
175     protected String getCellClass(HtmlSchedule schedule, ScheduleDay day, boolean even, int hour)
176     {
177         String cellClass = "free";
178         if (!day.isWorkingDay())
179         {
180             return getStyleClass(schedule, cellClass);
181         }
182 
183         if (hour >= schedule.getWorkingStartHour()
184                 && hour < schedule.getWorkingEndHour())
185         {
186             cellClass = even ? "even" : "uneven";
187         }
188 
189         return getStyleClass(schedule, cellClass);
190     }
191 
192     protected boolean isSelected(HtmlSchedule schedule, EntryWrapper entry)
193     {
194         ScheduleEntry selectedEntry = schedule.getModel().getSelectedEntry();
195 
196         if (selectedEntry == null)
197         {
198             return false;
199         }
200 
201         boolean returnboolean = selectedEntry.getId().equals(
202                 entry.entry.getId());
203 
204         return returnboolean;
205     }
206 
207     protected void maximizeEntries(EntryWrapper[] entries, int numberOfColumns)
208     {
209         for (int i = 0; i < entries.length; i++)
210         {
211             EntryWrapper entry = entries[i];
212 
213             //now see if we can expand the entry to the columns on the right
214             while (((entry.column + entry.colspan) < numberOfColumns)
215                     && entry.canFitInColumn(entry.column + entry.colspan))
216             {
217                 entry.colspan++;
218             }
219         }
220     }
221 
222     protected void scanEntries(EntryWrapper[] entries, int index)
223     {
224         if (entries.length <= 0)
225         {
226             return;
227         }
228 
229         EntryWrapper entry = entries[index];
230         entry.column = 0;
231 
232         //see what columns are already taken
233         for (int i = 0; i < index; i++)
234         {
235             if (entry.overlaps(entries[i]))
236             {
237                 entry.overlappingEntries.add(entries[i]);
238                 entries[i].overlappingEntries.add(entry);
239             }
240         }
241 
242         //find an available column
243         while (!entry.canFitInColumn(entry.column))
244         {
245             entry.column++;
246         }
247 
248         //recursively scan the remaining entries for overlaps
249         if (++index < entries.length)
250         {
251             scanEntries(entries, index);
252         }
253     }
254 
255     protected void writeGutter(FacesContext context, HtmlSchedule schedule,
256             ResponseWriter writer, boolean useIntervalLabels) throws IOException
257             {
258         final int rowHeight = getRowHeight(schedule);
259         final int headerHeight = rowHeight + 9;
260 
261         int startHour = getRenderedStartHour(schedule);
262         int endHour = getRenderedEndHour(schedule);
263 
264         DateFormat hourFormater = getDateFormat(context, schedule, 
265                 HtmlSchedule.HOUR_NOTATION_12.equals(schedule.getHourNotation()) ? "h" : "HH");
266         DateFormat minuteFormater = getDateFormat(context, schedule, 
267                 HtmlSchedule.HOUR_NOTATION_12.equals(schedule.getHourNotation()) ? "':'mma" : "mm");        
268         DateFormat shortMinuteFormater = getDateFormat(context, schedule, 
269                 HtmlSchedule.HOUR_NOTATION_12.equals(schedule.getHourNotation()) ? "a" : "mm");        
270 
271         ScheduleDay day = (ScheduleDay) schedule.getModel().iterator().next();
272 
273         writer.startElement(HTML.TABLE_ELEM, schedule);
274         writer.writeAttribute(HTML.CELLPADDING_ATTR, "0", null);
275         writer.writeAttribute(HTML.CELLSPACING_ATTR, "1", null);
276         writer.writeAttribute(HTML.CLASS_ATTR, getStyleClass(schedule, "background"), null);
277          writer.writeAttribute(HTML.STYLE_ATTR, "height: 100%", null);
278         writer.startElement(HTML.TBODY_ELEM, schedule);
279 
280         writer.startElement(HTML.TR_ELEM, schedule);
281 
282         // the header gutter
283         writer.startElement(HTML.TD_ELEM, schedule);
284         writer.writeAttribute(HTML.CLASS_ATTR, getStyleClass(schedule, "gutter"), null);
285         writer.writeAttribute(
286                 HTML.STYLE_ATTR,
287                 "height: "
288                 + headerHeight
289                 + "px; border-style: none; border-width: 0px; overflow: hidden; padding: 0px",
290                 null);
291         writer.startElement(HTML.DIV_ELEM, schedule);
292         writer.writeAttribute(HTML.STYLE_ATTR, "height: 1px; width: 56px", null);
293         writer.endElement(HTML.DIV_ELEM);
294         writer.endElement(HTML.TD_ELEM);
295         writer.endElement(HTML.TR_ELEM);
296 
297         // the intervals
298         Iterator intervalIt = day.getIntervals(startHour, endHour).iterator();
299 
300         boolean renderGutter = true;
301 
302         while (intervalIt.hasNext())
303         {
304             Interval interval = (Interval) intervalIt.next();
305             int intervalHeight = calcRowHeight(rowHeight, interval.getDuration()) - 1;
306 
307             // Don't render rows where the timespan is too small
308             if (intervalHeight <= 0)
309             {
310                 continue;
311             }
312 
313             if (!renderGutter)
314             {
315                 renderGutter = true;
316                 continue;
317             }
318 
319             writer.startElement(HTML.TR_ELEM, schedule);
320 
321             int gutterHeight = intervalHeight;
322 
323             if (day.getIntervals() == null && interval.getStartMinutes(getTimeZone(schedule)) == 0)
324             {
325                 gutterHeight = (gutterHeight * 2) + 1;
326                 renderGutter = false;
327             }                    
328 
329             //write the hours of the day on the left
330             //this only happens on even rows, or every hour
331             writer.startElement(HTML.TD_ELEM, schedule);
332             writer.writeAttribute(HTML.CLASS_ATTR, getStyleClass(schedule, "gutter"), null);
333             writer.writeAttribute(
334                     HTML.STYLE_ATTR,
335                     "height: " + gutterHeight
336                     + "px; border-style: none; border-width: 0px; overflow: hidden; padding: 0px",
337                     null);
338             if (interval.getDuration() >= HalfHourInterval.HALF_HOUR)
339             {
340                 if (!useIntervalLabels || interval.getLabel() == null)
341                 {
342                     writer.startElement(HTML.SPAN_ELEM, schedule);
343                     writer.writeAttribute(HTML.CLASS_ATTR, getStyleClass(schedule,
344                             renderGutter ? "minutes" : "hours"), null);
345                     writer.writeAttribute(HTML.STYLE_ATTR,
346                             "vertical-align: top; height: 100%; padding: 0px;",
347                             null);
348                     writer.writeText(hourFormater.format(interval.getStartTime()), null);
349                     writer.endElement(HTML.SPAN_ELEM);
350                 }
351                 writer.startElement(HTML.SPAN_ELEM, schedule);
352                 writer.writeAttribute(HTML.CLASS_ATTR, getStyleClass(schedule,
353                 "minutes"), null);
354                 writer.writeAttribute(HTML.STYLE_ATTR,
355                         "vertical-align: top; height: 100%; padding: 0px;",
356                         null);
357                 if (useIntervalLabels && interval.getLabel() != null)
358                 {
359                     writer.writeText(interval.getLabel(), null);
360                 }
361                 else
362                 {
363                     writer.writeText((renderGutter ? minuteFormater : shortMinuteFormater).format(interval.getStartTime()), null);
364                 }
365                 writer.endElement(HTML.SPAN_ELEM);
366             }
367             writer.endElement(HTML.TD_ELEM);
368             writer.endElement(HTML.TR_ELEM);
369         }
370 
371         writer.endElement(HTML.TBODY_ELEM);
372         writer.endElement(HTML.TABLE_ELEM);            
373             }
374 
375     protected void writeBackgroundStart(FacesContext context, HtmlSchedule schedule,
376             ResponseWriter writer) throws IOException
377             {
378         boolean repeatedIntervals = schedule.getModel().containsRepeatedIntervals();
379 
380         writer.startElement(HTML.TABLE_ELEM, schedule);
381         writer.writeAttribute(HTML.CELLPADDING_ATTR, "0", null);
382         writer.writeAttribute(HTML.CELLSPACING_ATTR, "0", null);
383         writer.writeAttribute(HTML.STYLE_ATTR, "width: 100%; height: 100%", null);
384         writer.startElement(HTML.TBODY_ELEM, schedule);
385         writer.startElement(HTML.TR_ELEM, schedule);
386         writer.startElement(HTML.TD_ELEM, schedule);
387         writer.writeAttribute(HTML.STYLE_ATTR, "width: 56px; vertical-align: top;", null);
388 
389         // Render gutter outside background, to allow it to have a flexible width
390         writeGutter(context, schedule, writer, repeatedIntervals);
391 
392         writer.endElement(HTML.TD_ELEM);
393         writer.startElement(HTML.TD_ELEM, schedule);
394         writer.writeAttribute(HTML.STYLE_ATTR, "width: 99%; vertical-align: top;", null);
395 
396         final String clientId = schedule.getClientId(context);
397         FormInfo parentFormInfo = RendererUtils.findNestingForm(schedule, context);
398         String formId = parentFormInfo == null ? null : parentFormInfo.getFormName();
399 
400         final int rowHeight = getRowHeight(schedule);
401         final int headerHeight = rowHeight + 9;
402         writer.startElement(HTML.DIV_ELEM, schedule);
403         writer.writeAttribute(HTML.CLASS_ATTR, getStyleClass(schedule,
404                 "background"), null);
405         writer
406         .writeAttribute(
407                 HTML.STYLE_ATTR,
408                 "position: relative; width: 100%; height: 100%; z-index: 0;",
409                 null);
410 
411         //background table for the schedule grid
412         writer.startElement(HTML.TABLE_ELEM, schedule);
413         writer.writeAttribute(HTML.CLASS_ATTR, getStyleClass(schedule,
414         "background"), null);
415         writer.writeAttribute(HTML.CELLPADDING_ATTR, "0", null);
416         writer.writeAttribute(HTML.CELLSPACING_ATTR, "0", null);
417         writer.writeAttribute(HTML.STYLE_ATTR, "width: 100%; height: 100%",
418                 null);
419         writer.startElement(HTML.TBODY_ELEM, schedule);
420         writer.startElement(HTML.TR_ELEM, schedule);
421 
422         float columnWidth = (schedule.getModel().size() == 0) ? 100
423                 : (100 / schedule.getModel().size());
424 
425         int startHour = getRenderedStartHour(schedule);
426         int endHour = getRenderedEndHour(schedule);
427 
428         ScheduleDay day = null;
429 
430         for (Iterator dayIterator = schedule.getModel().iterator(); dayIterator.hasNext();)
431         {
432             writer.startElement(HTML.TD_ELEM, schedule);
433 
434             writer.writeAttribute(HTML.STYLE_ATTR, "width: " + String.valueOf(columnWidth)+ "%", null);
435 
436             day = (ScheduleDay) dayIterator.next();
437 
438             writer.startElement(HTML.TABLE_ELEM, schedule);
439             writer.writeAttribute(HTML.CELLPADDING_ATTR, "0", null);
440             writer.writeAttribute(HTML.CELLSPACING_ATTR, "1", null);
441             writer.writeAttribute(HTML.STYLE_ATTR, "width: 100%; height: 100%",
442                     null);
443             writer.startElement(HTML.TBODY_ELEM, schedule);
444 
445             writer.startElement(HTML.TR_ELEM, schedule);
446 
447             // the header
448             final String dayHeaderId = clientId + "_header_" + ScheduleUtil.getDateId(day.getDate(), schedule.getModel().getTimeZone());
449 
450             writer.startElement(HTML.TH_ELEM, schedule);              
451             writer.writeAttribute(HTML.CLASS_ATTR, getStyleClass(schedule,
452                     "header"), null);
453             writer
454             .writeAttribute(
455                     HTML.STYLE_ATTR,
456                     "height: " + headerHeight + "px; vertical-align: top; border-style: none; border-width: 0px; overflow: hidden;",
457                     null);
458 
459             boolean isToday = ScheduleUtil.isSameDay(day.getDate(), new Date(), schedule.getModel().getTimeZone());
460 
461             // write the date
462             writer.startElement(HTML.ANCHOR_ELEM, schedule);
463             writer.writeAttribute(HTML.ID_ATTR, dayHeaderId, null);
464             writer.writeAttribute(HTML.HREF_ATTR, "#", null);
465             writer.writeAttribute(HTML.CLASS_ATTR, getStyleClass(schedule, "date") 
466                     + (isToday ? " today" : ""), null);
467             writer
468             .writeAttribute(
469                     HTML.STYLE_ATTR,
470                     "display: block; height: 50%; width: 100%; overflow: hidden; white-space: nowrap;",
471                     null);
472 
473             //register an onclick event listener to a column header which will
474             //be used to determine the date
475             if (!schedule.isReadonly() && schedule.isSubmitOnClick()) {
476                 writer.writeAttribute(
477                         HTML.ONCLICK_ATTR,
478                         "fireScheduleDateClicked(this, event, '"
479                         + formId + "', '"
480                         + clientId
481                         + "');",
482                         null);
483             }
484 
485             writer.writeText(getDateString(context, schedule, day.getDate()),
486                     null);
487             writer.endElement(HTML.ANCHOR_ELEM);
488 
489             // write the name of the holiday, if there is one
490             if ((day.getSpecialDayName() != null)
491                     && (day.getSpecialDayName().length() > 0))
492             {
493                 writer.startElement(HTML.SPAN_ELEM, schedule);
494                 writer.writeAttribute(HTML.CLASS_ATTR, getStyleClass(schedule,
495                         "holiday"), null);
496                 writer
497                 .writeAttribute(
498                         HTML.STYLE_ATTR,
499                         "height: 50%; width: 100%; overflow: hidden; white-space: nowrap;",
500                         null);
501                 writer.writeText(day.getSpecialDayName(), null);
502                 writer.endElement(HTML.SPAN_ELEM);
503             }
504 
505             writer.endElement(HTML.TH_ELEM);
506             writer.endElement(HTML.TR_ELEM);
507 
508             // the intervals
509             Iterator intervalIt = day.getIntervals(startHour, endHour).iterator();
510             boolean even = false;
511 
512             while (intervalIt.hasNext())
513             {
514                 Interval interval = (Interval) intervalIt.next();
515                 int intervalHeight = calcRowHeight(rowHeight, interval.getDuration()) - 1;
516 
517                 // Don't render rows where the timespan is too small
518                 if (intervalHeight <= 0)
519                 {
520                     continue;
521                 }
522 
523                 writer.startElement(HTML.TR_ELEM, schedule);
524 
525                 writer.startElement(HTML.TD_ELEM, schedule);
526                 writer.writeAttribute(HTML.CLASS_ATTR, getCellClass(schedule,
527                         day, even, interval.getStartHours(getTimeZone(schedule))), null);
528                 writer.writeAttribute(HTML.STYLE_ATTR,
529                         "overflow: hidden; height: " + intervalHeight + "px;", null);
530                 if (!repeatedIntervals && interval.getLabel() != null)
531                 {
532                     writer.write(interval.getLabel());
533                 }
534                 writer.endElement(HTML.TD_ELEM);
535 
536                 writer.endElement(HTML.TR_ELEM);
537 
538                 even = !even;
539             }
540 
541             writer.endElement(HTML.TBODY_ELEM);
542             writer.endElement(HTML.TABLE_ELEM);            
543 
544             writer.endElement(HTML.TD_ELEM);
545         }
546 
547         writer.endElement(HTML.TR_ELEM);
548         writer.endElement(HTML.TBODY_ELEM);
549         writer.endElement(HTML.TABLE_ELEM);
550     }
551 
552     protected void writeBackgroundEnd(ResponseWriter writer) throws IOException
553     {
554         writer.endElement(HTML.DIV_ELEM);
555     
556         writer.endElement(HTML.TD_ELEM);
557         writer.endElement(HTML.TR_ELEM);
558         writer.endElement(HTML.TBODY_ELEM);
559         writer.endElement(HTML.TABLE_ELEM);        
560     }
561 
562     /**
563      * Calculate an actual row height, given a specified height for a half hour duration.
564      * 
565      * @param halfHourHeight The height for a half hour duration
566      * @param duration The actual interval duration
567      * @return The height for the actual interval duration
568      */
569     private int calcRowHeight(int halfHourHeight, long duration) {
570 
571         return duration == HalfHourInterval.HALF_HOUR ? halfHourHeight : 
572             (int)((halfHourHeight / (float)(30 * 60 * 1000)) * duration);
573     }
574 
575     protected int getRenderedStartHour(HtmlSchedule schedule)
576     {
577         int startHour = schedule.getVisibleStartHour();
578 
579         //default behaviour: do not auto-expand the schedule to display all
580         //entries
581         if (!expandToFitEntries(schedule)) return startHour;
582 
583         for (Iterator dayIterator = schedule.getModel().iterator(); dayIterator.hasNext();)
584         {
585             ScheduleDay day = (ScheduleDay) dayIterator.next();
586             int dayStart = day.getFirstEventHour();
587 
588             if (dayStart < startHour) {
589                 startHour = dayStart;
590             }
591         }
592 
593         return startHour;
594     }
595 
596     protected int getRenderedEndHour(HtmlSchedule schedule)
597     {
598         int endHour = schedule.getVisibleEndHour();
599 
600         //default behaviour: do not auto-expand the schedule to display all
601         //entries
602         if (!expandToFitEntries(schedule)) return endHour;
603 
604         for (Iterator dayIterator = schedule.getModel().iterator(); dayIterator.hasNext();)
605         {
606             ScheduleDay day = (ScheduleDay) dayIterator.next();
607             int dayEnd = day.getLastEventHour();
608 
609             if (dayEnd > endHour) {
610                 endHour = dayEnd;
611             }
612         }
613 
614         return endHour;
615     }
616 
617     protected void writeEntries(FacesContext context, HtmlSchedule schedule,
618             ScheduleDay day, ResponseWriter writer) throws IOException
619     {
620         final String clientId = schedule.getClientId(context);
621         FormInfo parentFormInfo = RendererUtils.findNestingForm(schedule, context);
622         String formId = parentFormInfo == null ? null : parentFormInfo.getFormName();
623 
624         TreeSet entrySet = new TreeSet();
625 
626         for (Iterator entryIterator = day.iterator(); entryIterator.hasNext();)
627         {
628             entrySet.add(new EntryWrapper((ScheduleEntry) entryIterator.next(),
629                     day));
630         }
631 
632         EntryWrapper[] entries = (EntryWrapper[]) entrySet
633                 .toArray(new EntryWrapper[entrySet.size()]);
634 
635         //determine overlaps
636         scanEntries(entries, 0);
637 
638         //determine the number of columns within this day
639         int maxColumn = 0;
640 
641         for (Iterator entryIterator = entrySet.iterator(); entryIterator
642                 .hasNext();)
643         {
644             EntryWrapper wrapper = (EntryWrapper) entryIterator.next();
645             maxColumn = Math.max(wrapper.column, maxColumn);
646         }
647 
648         int numberOfColumns = maxColumn + 1;
649 
650         //make sure the entries take up all available space horizontally
651         maximizeEntries(entries, numberOfColumns);
652 
653         //now determine the width in percent of 1 column
654         float columnWidth = 100 / numberOfColumns;
655 
656         //and now draw the entries in the columns
657         for (Iterator entryIterator = entrySet.iterator(); entryIterator
658                 .hasNext();)
659         {
660             EntryWrapper wrapper = (EntryWrapper) entryIterator.next();
661             boolean selected = isSelected(schedule, wrapper);
662             //compose the CSS style for the entry box
663             StringBuffer entryStyle = new StringBuffer();
664             entryStyle.append(wrapper.getBounds(schedule, columnWidth));
665             String entryBorderColor = getEntryRenderer(schedule).getColor(
666                     context, schedule, wrapper.entry, selected);
667             if (entryBorderColor != null)
668             {
669                 entryStyle.append(" border-color: ");
670                 entryStyle.append(entryBorderColor);
671                 entryStyle.append(";");
672             }
673 
674             if (selected)
675             {
676                 writer.startElement(HTML.DIV_ELEM, schedule);
677                 writer.writeAttribute(HTML.CLASS_ATTR, getStyleClass(schedule,
678                         "entry-selected"), null);
679                 writer.writeAttribute(HTML.STYLE_ATTR, entryStyle.toString(),
680                         null);
681 
682                 //draw the tooltip
683                 if (schedule.isTooltip())
684                 {
685                     getEntryRenderer(schedule).renderToolTip(context, writer,
686                             schedule, wrapper.entry, selected);
687                 }
688 
689                 //draw the content
690                 getEntryRenderer(schedule).renderContent(context, writer,
691                         schedule, day, wrapper.entry, false, selected);
692                 writer.endElement(HTML.DIV_ELEM);
693             }
694             else
695             {
696                 //if the schedule is read-only, the entries should not be
697                 //hyperlinks
698                 writer.startElement(
699                         schedule.isReadonly() ? HTML.DIV_ELEM : HTML.ANCHOR_ELEM, schedule);
700 
701                 //draw the tooltip
702                 if (schedule.isTooltip())
703                 {
704                     getEntryRenderer(schedule).renderToolTip(context, writer,
705                             schedule, wrapper.entry, selected);
706                 }
707 
708                 if (!schedule.isReadonly())
709                 {
710                     writer.writeAttribute(HTML.HREF_ATTR, "#", null);
711 
712                     writer.writeAttribute(
713                             HTML.ONCLICK_ATTR,
714                             "fireEntrySelected('"
715                             + formId + "', '"
716                             + clientId + "', '"
717                             + wrapper.entry.getId()
718                             + "');",
719                             null);
720                 }
721 
722                 writer.writeAttribute(HTML.CLASS_ATTR, getEntryRenderer(schedule).getEntryClass(schedule, wrapper.entry), null);
723                 writer.writeAttribute(HTML.STYLE_ATTR, entryStyle.toString(),
724                         null);
725 
726                 //draw the content
727                 getEntryRenderer(schedule).renderContent(context, writer,
728                         schedule, day, wrapper.entry, false, selected);
729 
730                 writer.endElement(schedule.isReadonly() ? HTML.DIV_ELEM : HTML.ANCHOR_ELEM);
731             }
732         }
733     }
734 
735     protected void writeForegroundEnd(ResponseWriter writer) throws IOException
736     {
737         writer.endElement(HTML.TR_ELEM);
738         writer.endElement(HTML.TABLE_ELEM);
739         writer.endElement(HTML.DIV_ELEM);
740     }
741 
742     protected void writeForegroundStart(FacesContext context,
743             HtmlSchedule schedule, ResponseWriter writer) throws IOException
744     {
745         final int rowHeight = getRowHeight(schedule) - 1;
746         final int headerHeight = rowHeight + 10;
747 
748         writer.startElement(HTML.DIV_ELEM, schedule);
749         writer.writeAttribute(HTML.CLASS_ATTR, getStyleClass(schedule,
750                 "foreground"), null);
751         writer.writeAttribute(
752                 HTML.STYLE_ATTR,
753                 "position: absolute; left: 0px; top: " + headerHeight + "px; width: 100%; height: 100%;    z-index: 2;",
754                 null);
755 
756         writer.startElement(HTML.TABLE_ELEM, schedule);
757         writer.writeAttribute(HTML.CLASS_ATTR, getStyleClass(schedule,
758                 "foreground"), null);
759         writer.writeAttribute(HTML.CELLSPACING_ATTR, "1", null);
760         writer.writeAttribute(HTML.CELLPADDING_ATTR, "0", null);
761         writer.writeAttribute(HTML.STYLE_ATTR, "width: 100%; height: 100%",
762                 null);
763 
764         float columnWidth = (schedule.getModel().size() == 0) ? 100
765                 : (100 / schedule.getModel().size());
766 
767         for (Iterator dayIterator = schedule.getModel().iterator(); dayIterator
768                 .hasNext();)
769         {
770             dayIterator.next();
771             writer.startElement("col", schedule);
772 
773             writer.writeAttribute(HTML.STYLE_ATTR, 
774                     "width: " + String.valueOf(columnWidth) + "%;", null);
775             writer.endElement("col");
776         }
777 
778         writer.startElement(HTML.TR_ELEM, schedule);
779     }
780 
781     //~ Inner Classes ----------------------------------------------------------
782 
783     protected int getDefaultRowHeight()
784     {
785         return defaultRowHeightInPixels;
786     }
787 
788     /**
789      * In the detailed day renderer, we take the y coordinate of the mouse
790      * into account when determining the last clicked date.
791      */
792     protected Date determineLastClickedDate(HtmlSchedule schedule, String dateId, String yPos) {
793         //the dateId is the schedule client id + "_" + yyyyMMdd
794         String day = dateId.substring(dateId.lastIndexOf("_") + 1);
795         Date date = ScheduleUtil.getDateFromId(day, schedule.getModel().getTimeZone());
796 
797         Calendar cal = getCalendarInstance(schedule, date != null ? date : new Date());
798         cal.set(Calendar.HOUR_OF_DAY, getRenderedStartHour(schedule));
799         //OK, we have the date, let's determine the time
800         try {
801             int y = Integer.parseInt(yPos);
802             int halfHourHeight = getRowHeight(schedule);
803             int minutes = y * 30 / halfHourHeight;
804             cal.add(Calendar.MINUTE, minutes);
805         } catch (NumberFormatException nfe) {
806             log.debug("y position is not a number");
807         }
808         log.debug("last clicked datetime: " + cal.getTime());
809         return cal.getTime();
810     }
811 
812     /**
813      * <p>
814      * When the start- and endtime of an entry are the same, should the entry
815      * be rendered, fitting the entry box to the text? 
816      * </p>
817      * 
818      * @param component the component
819      * @return whether or not zero length entries should be rendered
820      */
821     protected boolean renderZeroLengthEntries(UIComponent component) {
822         if (component instanceof UIScheduleBase)
823         {
824             UIScheduleBase schedule = (UIScheduleBase) component;
825             return schedule.isRenderZeroLengthEntries();
826         } else {
827             return false;
828         }
829     }
830 
831     /**
832      * <p>
833      * When the start- and endtime of an entry are the same, should the entry
834      * be rendered, fitting the entry box to the text?
835      * </p>
836      *
837      * @param component the component
838      * @return whether or not zero length entries should be rendered
839      */
840     protected boolean expandToFitEntries(UIComponent component) {
841         if (component instanceof HtmlSchedule)
842         {
843             HtmlSchedule schedule = (HtmlSchedule) component;
844             return schedule.isExpandToFitEntries();
845         } 
846         return false;
847     }
848 
849 
850     protected class EntryWrapper implements Comparable
851     {
852         //~ Static fields/initializers -----------------------------------------
853 
854         private static final int HALF_HOUR = 1000 * 60 * 30;
855 
856         //~ Instance fields ----------------------------------------------------
857 
858         private final ScheduleDay day;
859         private final ScheduleEntry entry;
860         private final TreeSet overlappingEntries;
861         private int colspan;
862         private int column;
863 
864         //~ Constructors -------------------------------------------------------
865 
866         EntryWrapper(ScheduleEntry entry, ScheduleDay day)
867         {
868             this.entry = entry;
869             this.day = day;
870             this.column = 0;
871             this.colspan = 1;
872             this.overlappingEntries = new TreeSet();
873         }
874 
875         //~ Methods ------------------------------------------------------------
876 
877         /**
878          * @see java.lang.Comparable#compareTo(java.lang.Object)
879          */
880         public int compareTo(Object o)
881         {
882             return comparator.compare(entry, o);
883         }
884 
885         /**
886          * @see java.lang.Object#equals(java.lang.Object)
887          */
888         public boolean equals(Object o)
889         {
890             if (o instanceof EntryWrapper)
891             {
892                 EntryWrapper other = (EntryWrapper) o;
893 
894                 boolean returnboolean = (entry.getStartTime()
895                         .equals(other.entry.getStartTime()))
896                         && (entry.getEndTime().equals(other.entry.getEndTime()))
897                         && (entry.getId().equals(other.entry.getId()))
898                         && (day.equals(other.day));
899                 /*
900                  new EqualsBuilder().append(
901                  entry.getStartTime(), other.entry.getStartTime()
902                  ).append(entry.getEndTime(), other.entry.getEndTime())
903                  .append(
904                  entry.getId(), other.entry.getId()
905                  ).append(day, other.day).isEquals();
906                  */
907                 return returnboolean;
908             }
909 
910             return false;
911         }
912 
913         /**
914          * @see java.lang.Object#hashCode()
915          */
916         public int hashCode()
917         {
918             int returnint = entry.getStartTime().hashCode()
919                     ^ entry.getEndTime().hashCode() ^ entry.getId().hashCode();
920 
921             return returnint;
922         }
923 
924         /**
925          * <p>
926          * Determine the bounds of this entry, in CSS position attributes
927          * </p>
928          *
929          * @param schedule the schedule
930          * @param columnWidth the width of a column
931          *
932          * @return the bounds
933          */
934         String getBounds(HtmlSchedule schedule, float columnWidth)
935         {
936             int rowHeight = getRowHeight(schedule);
937             float width = (columnWidth * colspan) - 0.5f;
938             float left = column * columnWidth;
939             Calendar cal = getCalendarInstance(schedule, day.getDate());
940 
941             int curyear = cal.get(Calendar.YEAR);
942             int curmonth = cal.get(Calendar.MONTH);
943             int curday = cal.get(Calendar.DATE);
944 
945             cal.setTime(entry.getStartTime());
946             cal.set(curyear, curmonth, curday);
947 
948             long startMillis = cal.getTimeInMillis();
949             cal.set(Calendar.HOUR_OF_DAY, getRenderedStartHour(schedule));
950             cal.set(Calendar.MINUTE, 0);
951             cal.set(Calendar.SECOND, 0);
952             cal.set(Calendar.MILLISECOND, 0);
953 
954             long visibleStartMillis = cal.getTimeInMillis();
955             startMillis = day.equalsDate(entry.getStartTime()) ? Math.max(
956                     startMillis, visibleStartMillis) : visibleStartMillis;
957             cal.setTime(entry.getEndTime());
958             cal.set(curyear, curmonth, curday);
959 
960             long endMillis = cal.getTimeInMillis();
961             cal.set(Calendar.HOUR_OF_DAY, getRenderedEndHour(schedule));
962             cal.set(Calendar.MINUTE, 0);
963             cal.set(Calendar.SECOND, 0);
964             cal.set(Calendar.MILLISECOND, 0);
965 
966             long visibleEndMillis = cal.getTimeInMillis();
967             endMillis = day.equalsDate(entry.getEndTime()) ? Math.min(
968                     endMillis, visibleEndMillis) : visibleEndMillis;
969 
970             int top = (int) (((startMillis - visibleStartMillis) * rowHeight) / HALF_HOUR);
971             int height = (int) (((endMillis - startMillis) * rowHeight) / HALF_HOUR);
972             StringBuffer buffer = new StringBuffer();
973 
974             boolean entryVisible = height > 0 || renderZeroLengthEntries(schedule);
975 
976             if (!entryVisible)
977             {
978                 buffer.append("visibility: hidden; ");
979             }
980             buffer.append("position: absolute; height: ");
981             if (height > 2)
982             {
983                 // Adjust for the width of the border
984                 buffer.append((height - 2) + "px");
985             } else if (height > 0)
986             {
987                 buffer.append(height + "px");
988             } else if (entryVisible)
989             {
990                 buffer.append("auto");
991             } else
992             {
993                 buffer.append("0px");
994             }
995             buffer.append("; top: ");
996             buffer.append(top);
997             buffer.append("px; left: ");
998             buffer.append(left);
999             buffer.append("%; width: ");
1000             buffer.append(width);
1001             buffer
1002                     .append("%; padding: 0px; overflow: hidden; border-width: 1.0px; border-style:solid;");
1003 
1004             return buffer.toString();
1005         }
1006 
1007         /**
1008          * <p>
1009          * Can this entry fit in the specified column?
1010          * </p>
1011          *
1012          * @param column the column
1013          *
1014          * @return whether the entry fits
1015          */
1016         boolean canFitInColumn(int column)
1017         {
1018             for (Iterator overlapIterator = overlappingEntries.iterator(); overlapIterator
1019                     .hasNext();)
1020             {
1021                 EntryWrapper overlap = (EntryWrapper) overlapIterator.next();
1022 
1023                 if (overlap.column == column)
1024                 {
1025                     return false;
1026                 }
1027             }
1028 
1029             return true;
1030         }
1031 
1032         /**
1033          * <p>
1034          * What is the minimum end time allocated to this event?
1035          * Where the event has a duration, the end time of the event
1036          * is the minimum end time.<br />
1037          * Where the event has no duration, a minimum end time of half
1038          * and hour after the start is implemented.
1039          * </p>
1040          * @return The minimum end time of the event
1041          */
1042         Date minimumEndTime() {
1043             Date start = entry.getStartTime();
1044             Date end = entry.getEndTime();
1045 
1046             return end != null ?
1047                     (end.after(start) ? end : new Date(start.getTime() + HALF_HOUR))
1048                     : null;
1049         }
1050 
1051         /**
1052          * <p>
1053          * Does this entry overlap with another?
1054          * </p>
1055          *
1056          * @param other the other entry
1057          *
1058          * @return whether the entries overlap
1059          */
1060         boolean overlaps(EntryWrapper other)
1061         {
1062             Date start = entry.getStartTime();
1063             Date end = minimumEndTime();
1064 
1065             if ((start == null) || (end == null))
1066             {
1067                 return false;
1068             }
1069 
1070             boolean returnboolean = (start.before(
1071                     other.minimumEndTime()) && end.after(
1072                             other.entry.getStartTime()));
1073 
1074             return returnboolean;
1075         }
1076     }
1077 
1078 
1079     protected int getRowHeight(UIScheduleBase schedule)
1080     {
1081         if (schedule != null) {
1082             int height = schedule.getDetailedRowHeight();
1083             return height <= 0 ? getDefaultRowHeight() : height;
1084         }
1085         return getDefaultRowHeight();
1086     }
1087 
1088     private TimeZone getTimeZone(UIScheduleBase schedule)
1089     {
1090 
1091         return schedule != null && schedule.getModel().getTimeZone() != null ? 
1092                 schedule.getModel().getTimeZone() 
1093                 : TimeZone.getDefault();
1094     }
1095 }
1096 //The End