├── .gitignore ├── LICENSE ├── README.md ├── screenshots ├── DayCalendar.png └── WeekCalendar.png ├── src └── com │ └── davidmoodie │ └── SwingCalendar │ ├── Calendar.java │ ├── CalendarEmptyClickEvent.java │ ├── CalendarEmptyClickListener.java │ ├── CalendarEvent.java │ ├── CalendarEventClickEvent.java │ ├── CalendarEventClickListener.java │ ├── DayCalendar.java │ ├── Week.java │ └── WeekCalendar.java └── test ├── DayCalendarTest.java └── WeekCalendarTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 12 | hs_err_pid* 13 | 14 | .idea 15 | *.iml -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 David Moodie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwingCalendar 2 | 3 | A Weekly/Daily Swing JComponent Calendar View inspired by Google Calendar. 4 | 5 | *Note that this project makes use of Java 8 features such as Lambdas.* 6 | 7 | ## Screenshots 8 | 9 | Below, the current day these were taken was Friday, 18th November 2016 at 11:15am. 10 | 11 | ![Week View Calendar](/screenshots/WeekCalendar.png?raw=true "Week View Calendar") 12 | ![Day View Calendar](/screenshots/DayCalendar.png?raw=true "Day View Calendar") 13 | 14 | ## Info 15 | 16 | I originally wrote this component as part of a group project for the COM2002 HCI module at The University of Sheffield. 17 | There are countless improvements that can and should be made and I intend to develop this into a more extensible and publicly consumable component should I have the time (feel free to put in some PRs!). 18 | 19 | As it stands, the calendar has a Weekly and Daily view and Events that fire on a CalendarEvent being clicked or an empty slot being clicked. These were particularly useful for the application this was originally intended for. 20 | 21 | ## Current limitations: 22 | 23 | - Does not support showing weekends but this shouldn't be too hard to fix. 24 | - Only shows times between 9:00 and 17:00. Again this shouldn't be too difficult to fix. 25 | - Only supports events that occur within a single calendar day. -------------------------------------------------------------------------------- /screenshots/DayCalendar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davejm/SwingCalendar/82f5562a0a09715b1a02f7376218db4e83c815d3/screenshots/DayCalendar.png -------------------------------------------------------------------------------- /screenshots/WeekCalendar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davejm/SwingCalendar/82f5562a0a09715b1a02f7376218db4e83c815d3/screenshots/WeekCalendar.png -------------------------------------------------------------------------------- /src/com/davidmoodie/SwingCalendar/Calendar.java: -------------------------------------------------------------------------------- 1 | package com.davidmoodie.SwingCalendar; 2 | 3 | import javax.swing.*; 4 | import javax.swing.event.EventListenerList; 5 | import java.awt.*; 6 | import java.awt.event.MouseAdapter; 7 | import java.awt.event.MouseEvent; 8 | import java.awt.geom.Line2D; 9 | import java.awt.geom.Rectangle2D; 10 | import java.time.DayOfWeek; 11 | import java.time.LocalDate; 12 | import java.time.LocalDateTime; 13 | import java.time.LocalTime; 14 | import java.time.format.TextStyle; 15 | import java.time.temporal.ChronoUnit; 16 | import java.util.ArrayList; 17 | import java.util.Locale; 18 | 19 | public abstract class Calendar extends JComponent { 20 | protected static final LocalTime START_TIME = LocalTime.of(9, 0); 21 | protected static final LocalTime END_TIME = LocalTime.of(17, 0); 22 | 23 | protected static final int MIN_WIDTH = 600; 24 | protected static final int MIN_HEIGHT = MIN_WIDTH; 25 | 26 | protected static final int HEADER_HEIGHT = 30; 27 | protected static final int TIME_COL_WIDTH = 100; 28 | 29 | // An estimate of the width of a single character (not exact but good 30 | // enough) 31 | private static final int FONT_LETTER_PIXEL_WIDTH = 7; 32 | private ArrayList events; 33 | private double timeScale; 34 | private double dayWidth; 35 | private Graphics2D g2; 36 | 37 | private EventListenerList listenerList = new EventListenerList(); 38 | 39 | public Calendar() { 40 | this(new ArrayList<>()); 41 | } 42 | 43 | Calendar(ArrayList events) { 44 | this.events = events; 45 | setupEventListeners(); 46 | setupTimer(); 47 | } 48 | 49 | public static LocalTime roundTime(LocalTime time, int minutes) { 50 | LocalTime t = time; 51 | 52 | if (t.getMinute() % minutes > minutes / 2) { 53 | t = t.plusMinutes(minutes - (t.getMinute() % minutes)); 54 | } else if (t.getMinute() % minutes < minutes / 2) { 55 | t = t.minusMinutes(t.getMinute() % minutes); 56 | } 57 | 58 | return t; 59 | } 60 | 61 | private void setupEventListeners() { 62 | this.addMouseListener(new MouseAdapter() { 63 | @Override 64 | public void mouseClicked(MouseEvent e) { 65 | super.mouseClicked(e); 66 | if (!checkCalendarEventClick(e.getPoint())) { 67 | checkCalendarEmptyClick(e.getPoint()); 68 | } 69 | } 70 | }); 71 | } 72 | 73 | protected abstract boolean dateInRange(LocalDate date); 74 | 75 | private boolean checkCalendarEventClick(Point p) { 76 | double x0, x1, y0, y1; 77 | for (CalendarEvent event : events) { 78 | if (!dateInRange(event.getDate())) continue; 79 | 80 | x0 = dayToPixel(event.getDate().getDayOfWeek()); 81 | y0 = timeToPixel(event.getStart()); 82 | x1 = dayToPixel(event.getDate().getDayOfWeek()) + dayWidth; 83 | y1 = timeToPixel(event.getEnd()); 84 | 85 | if (p.getX() >= x0 && p.getX() <= x1 && p.getY() >= y0 && p.getY() <= y1) { 86 | fireCalendarEventClick(event); 87 | return true; 88 | } 89 | } 90 | return false; 91 | } 92 | 93 | private boolean checkCalendarEmptyClick(Point p) { 94 | final double x0 = dayToPixel(getStartDay()); 95 | final double x1 = dayToPixel(getEndDay()) + dayWidth; 96 | final double y0 = timeToPixel(START_TIME); 97 | final double y1 = timeToPixel(END_TIME); 98 | 99 | if (p.getX() >= x0 && p.getX() <= x1 && p.getY() >= y0 && p.getY() <= y1) { 100 | LocalDate date = getDateFromDay(pixelToDay(p.getX())); 101 | fireCalendarEmptyClick(LocalDateTime.of(date, pixelToTime(p.getY()))); 102 | return true; 103 | } 104 | return false; 105 | } 106 | 107 | protected abstract LocalDate getDateFromDay(DayOfWeek day); 108 | 109 | // CalendarEventClick methods 110 | 111 | public void addCalendarEventClickListener(CalendarEventClickListener l) { 112 | listenerList.add(CalendarEventClickListener.class, l); 113 | } 114 | 115 | public void removeCalendarEventClickListener(CalendarEventClickListener l) { 116 | listenerList.remove(CalendarEventClickListener.class, l); 117 | } 118 | 119 | // Notify all listeners that have registered interest for 120 | // notification on this event type. 121 | private void fireCalendarEventClick(CalendarEvent calendarEvent) { 122 | // Guaranteed to return a non-null array 123 | Object[] listeners = listenerList.getListenerList(); 124 | // Process the listeners last to first, notifying 125 | // those that are interested in this event 126 | CalendarEventClickEvent calendarEventClickEvent; 127 | for (int i = listeners.length - 2; i >= 0; i -= 2) { 128 | if (listeners[i] == CalendarEventClickListener.class) { 129 | calendarEventClickEvent = new CalendarEventClickEvent(this, calendarEvent); 130 | ((CalendarEventClickListener) listeners[i + 1]).calendarEventClick(calendarEventClickEvent); 131 | } 132 | } 133 | } 134 | 135 | // CalendarEmptyClick methods 136 | 137 | public void addCalendarEmptyClickListener(CalendarEmptyClickListener l) { 138 | listenerList.add(CalendarEmptyClickListener.class, l); 139 | } 140 | 141 | public void removeCalendarEmptyClickListener(CalendarEmptyClickListener l) { 142 | listenerList.remove(CalendarEmptyClickListener.class, l); 143 | } 144 | 145 | private void fireCalendarEmptyClick(LocalDateTime dateTime) { 146 | Object[] listeners = listenerList.getListenerList(); 147 | CalendarEmptyClickEvent calendarEmptyClickEvent; 148 | for (int i = listeners.length - 2; i >= 0; i -= 2) { 149 | if (listeners[i] == CalendarEmptyClickListener.class) { 150 | calendarEmptyClickEvent = new CalendarEmptyClickEvent(this, dateTime); 151 | ((CalendarEmptyClickListener) listeners[i + 1]).calendarEmptyClick(calendarEmptyClickEvent); 152 | } 153 | } 154 | } 155 | 156 | private void calculateScaleVars() { 157 | int width = getWidth(); 158 | int height = getHeight(); 159 | 160 | if (width < MIN_WIDTH) { 161 | width = MIN_WIDTH; 162 | } 163 | 164 | if (height < MIN_HEIGHT) { 165 | height = MIN_HEIGHT; 166 | } 167 | 168 | // Units are pixels per second 169 | timeScale = (double) (height - HEADER_HEIGHT) / (END_TIME.toSecondOfDay() - START_TIME.toSecondOfDay()); 170 | dayWidth = (width - TIME_COL_WIDTH) / numDaysToShow(); 171 | } 172 | 173 | protected abstract int numDaysToShow(); 174 | 175 | // Gives x val of left most pixel for day col 176 | protected abstract double dayToPixel(DayOfWeek dayOfWeek); 177 | 178 | private double timeToPixel(LocalTime time) { 179 | return ((time.toSecondOfDay() - START_TIME.toSecondOfDay()) * timeScale) + HEADER_HEIGHT; 180 | } 181 | 182 | private LocalTime pixelToTime(double y) { 183 | return LocalTime.ofSecondOfDay((int) ((y - HEADER_HEIGHT) / timeScale) + START_TIME.toSecondOfDay()).truncatedTo(ChronoUnit.MINUTES); 184 | } 185 | 186 | private DayOfWeek pixelToDay(double x) { 187 | double pixel; 188 | DayOfWeek day; 189 | for (int i = getStartDay().getValue(); i <= getEndDay().getValue(); i++) { 190 | day = DayOfWeek.of(i); 191 | pixel = dayToPixel(day); 192 | if (x >= pixel && x < pixel + dayWidth) { 193 | return day; 194 | } 195 | } 196 | return null; 197 | } 198 | 199 | @Override 200 | protected void paintComponent(Graphics g) { 201 | calculateScaleVars(); 202 | g2 = (Graphics2D) g; 203 | 204 | // Rendering hints try to turn anti-aliasing on which improves quality 205 | g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 206 | 207 | // Set background to white 208 | g2.setColor(Color.white); 209 | g2.fillRect(0, 0, getWidth(), getHeight()); 210 | 211 | // Set paint colour to black 212 | g2.setColor(Color.black); 213 | 214 | drawDayHeadings(); 215 | drawTodayShade(); 216 | drawGrid(); 217 | drawTimes(); 218 | drawEvents(); 219 | drawCurrentTimeLine(); 220 | } 221 | 222 | protected abstract DayOfWeek getStartDay(); 223 | 224 | protected abstract DayOfWeek getEndDay(); 225 | 226 | private void drawDayHeadings() { 227 | int y = 20; 228 | int x; 229 | LocalDate day; 230 | DayOfWeek dayOfWeek; 231 | 232 | for (int i = getStartDay().getValue(); i <= getEndDay().getValue(); i++) { 233 | dayOfWeek = DayOfWeek.of(i); 234 | day = getDateFromDay(dayOfWeek); 235 | 236 | String text = dayOfWeek.getDisplayName(TextStyle.SHORT, Locale.ENGLISH) + " " + day.getDayOfMonth() + "/" + day.getMonthValue(); 237 | x = (int) (dayToPixel(DayOfWeek.of(i)) + (dayWidth / 2) - (FONT_LETTER_PIXEL_WIDTH * text.length() / 2)); 238 | g2.drawString(text, x, y); 239 | } 240 | } 241 | 242 | private void drawGrid() { 243 | // Save the original colour 244 | final Color ORIG_COLOUR = g2.getColor(); 245 | 246 | // Set colour to grey with half alpha (opacity) 247 | Color alphaGray = new Color(128, 128, 128, 128); 248 | Color alphaGrayLighter = new Color(200, 200, 200, 128); 249 | g2.setColor(alphaGray); 250 | 251 | // Draw vertical grid lines 252 | double x; 253 | for (int i = getStartDay().getValue(); i <= getEndDay().getValue(); i++) { 254 | x = dayToPixel(DayOfWeek.of(i)); 255 | g2.draw(new Line2D.Double(x, HEADER_HEIGHT, x, timeToPixel(END_TIME))); 256 | } 257 | 258 | // Draw horizontal grid lines 259 | double y; 260 | int x1; 261 | for (LocalTime time = START_TIME; time.compareTo(END_TIME) <= 0; time = time.plusMinutes(30)) { 262 | y = timeToPixel(time); 263 | if (time.getMinute() == 0) { 264 | g2.setColor(alphaGray); 265 | x1 = 0; 266 | } else { 267 | g2.setColor(alphaGrayLighter); 268 | x1 = TIME_COL_WIDTH; 269 | } 270 | g2.draw(new Line2D.Double(x1, y, dayToPixel(getEndDay()) + dayWidth, y)); 271 | } 272 | 273 | // Reset the graphics context's colour 274 | g2.setColor(ORIG_COLOUR); 275 | } 276 | 277 | private void drawTodayShade() { 278 | LocalDate today = LocalDate.now(); 279 | 280 | // Check that date range being viewed is current date range 281 | if (!dateInRange(today)) return; 282 | 283 | final double x = dayToPixel(today.getDayOfWeek()); 284 | final double y = timeToPixel(START_TIME); 285 | final double width = dayWidth; 286 | final double height = timeToPixel(END_TIME) - timeToPixel(START_TIME); 287 | 288 | final Color origColor = g2.getColor(); 289 | Color alphaGray = new Color(200, 200, 200, 64); 290 | g2.setColor(alphaGray); 291 | g2.fill(new Rectangle2D.Double(x, y, width, height)); 292 | g2.setColor(origColor); 293 | } 294 | 295 | private void drawCurrentTimeLine() { 296 | LocalDate today = LocalDate.now(); 297 | 298 | // Check that date range being viewed is current date range 299 | if (!dateInRange(today)) return; 300 | 301 | final double x0 = dayToPixel(today.getDayOfWeek()); 302 | final double x1 = dayToPixel(today.getDayOfWeek()) + dayWidth; 303 | final double y = timeToPixel(LocalTime.now()); 304 | 305 | final Color origColor = g2.getColor(); 306 | final Stroke origStroke = g2.getStroke(); 307 | 308 | g2.setColor(new Color(255, 127, 110)); 309 | g2.setStroke(new BasicStroke(2)); 310 | g2.draw(new Line2D.Double(x0, y, x1, y)); 311 | 312 | g2.setColor(origColor); 313 | g2.setStroke(origStroke); 314 | } 315 | 316 | private void drawTimes() { 317 | int y; 318 | for (LocalTime time = START_TIME; time.compareTo(END_TIME) <= 0; time = time.plusHours(1)) { 319 | y = (int) timeToPixel(time) + 15; 320 | g2.drawString(time.toString(), TIME_COL_WIDTH - (FONT_LETTER_PIXEL_WIDTH * time.toString().length()) - 5, y); 321 | } 322 | } 323 | 324 | private void drawEvents() { 325 | double x; 326 | double y0; 327 | 328 | for (CalendarEvent event : events) { 329 | if (!dateInRange(event.getDate())) continue; 330 | 331 | x = dayToPixel(event.getDate().getDayOfWeek()); 332 | y0 = timeToPixel(event.getStart()); 333 | 334 | Rectangle2D rect = new Rectangle2D.Double(x, y0, dayWidth, (timeToPixel(event.getEnd()) - timeToPixel(event.getStart()))); 335 | Color origColor = g2.getColor(); 336 | g2.setColor(event.getColor()); 337 | g2.fill(rect); 338 | g2.setColor(origColor); 339 | 340 | // Draw time header 341 | 342 | // Store the current font state 343 | Font origFont = g2.getFont(); 344 | 345 | final float fontSize = origFont.getSize() - 1.6F; 346 | 347 | // Create a new font with same properties but bold 348 | Font newFont = origFont.deriveFont(Font.BOLD, fontSize); 349 | g2.setFont(newFont); 350 | 351 | g2.drawString(event.getStart() + " - " + event.getEnd(), (int) x + 5, (int) y0 + 11); 352 | 353 | // Unbolden 354 | g2.setFont(origFont.deriveFont(fontSize)); 355 | 356 | // Draw the event's text 357 | g2.drawString(event.getText(), (int) x + 5, (int) y0 + 23); 358 | 359 | // Reset font 360 | g2.setFont(origFont); 361 | } 362 | } 363 | 364 | protected double getDayWidth() { 365 | return dayWidth; 366 | } 367 | 368 | // Repaints every minute to update the current time line 369 | private void setupTimer() { 370 | Timer timer = new Timer(1000*60, e -> repaint()); 371 | timer.start(); 372 | } 373 | 374 | protected abstract void setRangeToToday(); 375 | 376 | public void goToToday() { 377 | setRangeToToday(); 378 | repaint(); 379 | } 380 | 381 | public void addEvent(CalendarEvent event) { 382 | events.add(event); 383 | repaint(); 384 | } 385 | 386 | public boolean removeEvent(CalendarEvent event) { 387 | boolean removed = events.remove(event); 388 | repaint(); 389 | return removed; 390 | } 391 | 392 | public void setEvents(ArrayList events) { 393 | this.events = events; 394 | repaint(); 395 | } 396 | } 397 | -------------------------------------------------------------------------------- /src/com/davidmoodie/SwingCalendar/CalendarEmptyClickEvent.java: -------------------------------------------------------------------------------- 1 | package com.davidmoodie.SwingCalendar; 2 | 3 | import java.awt.*; 4 | import java.time.LocalDateTime; 5 | 6 | public class CalendarEmptyClickEvent extends AWTEvent { 7 | private LocalDateTime dateTime; 8 | 9 | public CalendarEmptyClickEvent(Object source, LocalDateTime dateTime) { 10 | super(source, 0); 11 | this.dateTime = dateTime; 12 | } 13 | 14 | public LocalDateTime getDateTime() { 15 | return dateTime; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/com/davidmoodie/SwingCalendar/CalendarEmptyClickListener.java: -------------------------------------------------------------------------------- 1 | package com.davidmoodie.SwingCalendar; 2 | 3 | import java.util.EventListener; 4 | 5 | public interface CalendarEmptyClickListener extends EventListener { 6 | // Event dispatch methods 7 | void calendarEmptyClick(CalendarEmptyClickEvent e); 8 | } 9 | -------------------------------------------------------------------------------- /src/com/davidmoodie/SwingCalendar/CalendarEvent.java: -------------------------------------------------------------------------------- 1 | package com.davidmoodie.SwingCalendar; 2 | 3 | import java.awt.*; 4 | import java.time.LocalDate; 5 | import java.time.LocalTime; 6 | 7 | public class CalendarEvent { 8 | 9 | private static final Color DEFAULT_COLOR = Color.PINK; 10 | 11 | private LocalDate date; 12 | private LocalTime start; 13 | private LocalTime end; 14 | private String text; 15 | private Color color; 16 | 17 | public CalendarEvent(LocalDate date, LocalTime start, LocalTime end, String text) { 18 | this(date, start, end, text, DEFAULT_COLOR); 19 | } 20 | 21 | public CalendarEvent(LocalDate date, LocalTime start, LocalTime end, String text, Color color) { 22 | this.date = date; 23 | this.start = start; 24 | this.end = end; 25 | this.text = text; 26 | this.color = color; 27 | } 28 | 29 | public LocalDate getDate() { 30 | return date; 31 | } 32 | 33 | public void setDate(LocalDate date) { 34 | this.date = date; 35 | } 36 | 37 | public LocalTime getStart() { 38 | return start; 39 | } 40 | 41 | public void setStart(LocalTime start) { 42 | this.start = start; 43 | } 44 | 45 | public LocalTime getEnd() { 46 | return end; 47 | } 48 | 49 | public void setEnd(LocalTime end) { 50 | this.end = end; 51 | } 52 | 53 | public String getText() { 54 | return text; 55 | } 56 | 57 | public void setText(String text) { 58 | this.text = text; 59 | } 60 | 61 | public String toString() { 62 | return getDate() + " " + getStart() + "-" + getEnd() + ". " + getText(); 63 | } 64 | 65 | public Color getColor() { 66 | return color; 67 | } 68 | 69 | @Override 70 | public boolean equals(Object o) { 71 | if (this == o) return true; 72 | if (o == null || getClass() != o.getClass()) return false; 73 | 74 | CalendarEvent that = (CalendarEvent) o; 75 | 76 | if (!date.equals(that.date)) return false; 77 | if (!start.equals(that.start)) return false; 78 | return end.equals(that.end); 79 | 80 | } 81 | 82 | @Override 83 | public int hashCode() { 84 | int result = date.hashCode(); 85 | result = 31 * result + start.hashCode(); 86 | result = 31 * result + end.hashCode(); 87 | return result; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/com/davidmoodie/SwingCalendar/CalendarEventClickEvent.java: -------------------------------------------------------------------------------- 1 | package com.davidmoodie.SwingCalendar; 2 | 3 | import java.awt.*; 4 | 5 | public class CalendarEventClickEvent extends AWTEvent { 6 | 7 | private CalendarEvent calendarEvent; 8 | 9 | public CalendarEventClickEvent(Object source, CalendarEvent calendarEvent) { 10 | super(source, 0); 11 | this.calendarEvent = calendarEvent; 12 | } 13 | 14 | public CalendarEvent getCalendarEvent() { 15 | return calendarEvent; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/com/davidmoodie/SwingCalendar/CalendarEventClickListener.java: -------------------------------------------------------------------------------- 1 | package com.davidmoodie.SwingCalendar; 2 | 3 | import java.util.EventListener; 4 | 5 | public interface CalendarEventClickListener extends EventListener { 6 | // Event dispatch methods 7 | void calendarEventClick(CalendarEventClickEvent e); 8 | } 9 | -------------------------------------------------------------------------------- /src/com/davidmoodie/SwingCalendar/DayCalendar.java: -------------------------------------------------------------------------------- 1 | package com.davidmoodie.SwingCalendar; 2 | 3 | import java.time.DayOfWeek; 4 | import java.time.LocalDate; 5 | import java.util.ArrayList; 6 | 7 | public class DayCalendar extends Calendar { 8 | 9 | private LocalDate calDate; 10 | 11 | public DayCalendar(ArrayList events) { 12 | super(events); 13 | calDate = LocalDate.now(); 14 | } 15 | 16 | @Override 17 | protected boolean dateInRange(LocalDate date) { 18 | return calDate.equals(date); 19 | } 20 | 21 | @Override 22 | protected LocalDate getDateFromDay(DayOfWeek day) { 23 | return calDate; 24 | } 25 | 26 | @Override 27 | protected int numDaysToShow() { 28 | return 1; 29 | } 30 | 31 | @Override 32 | protected DayOfWeek getStartDay() { 33 | return calDate.getDayOfWeek(); 34 | } 35 | 36 | @Override 37 | protected DayOfWeek getEndDay() { 38 | return calDate.getDayOfWeek(); 39 | } 40 | 41 | @Override 42 | protected void setRangeToToday() { 43 | calDate = LocalDate.now(); 44 | } 45 | 46 | @Override 47 | protected double dayToPixel(DayOfWeek dayOfWeek) { 48 | return TIME_COL_WIDTH; 49 | } 50 | 51 | public void nextDay() { 52 | calDate = calDate.plusDays(1); 53 | repaint(); 54 | } 55 | 56 | public void prevDay() { 57 | calDate = calDate.minusDays(1); 58 | repaint(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/com/davidmoodie/SwingCalendar/Week.java: -------------------------------------------------------------------------------- 1 | package com.davidmoodie.SwingCalendar; 2 | 3 | import java.time.DayOfWeek; 4 | import java.time.LocalDate; 5 | import java.util.ArrayList; 6 | 7 | public class Week { 8 | 9 | private ArrayList days; 10 | 11 | // Gets week variables from any date (can be within week) 12 | public Week(LocalDate date) { 13 | days = new ArrayList<>(); 14 | LocalDate monday = getStartOfWeek(date); 15 | days.add(monday); 16 | for (int i = 1; i < 5; i++) { 17 | days.add(monday.plusDays(i)); 18 | } 19 | } 20 | 21 | public static LocalDate getStartOfWeek(LocalDate date) { 22 | LocalDate day = date; 23 | while (day.getDayOfWeek() != DayOfWeek.MONDAY) { 24 | day = day.minusDays(1); 25 | } 26 | return day; 27 | } 28 | 29 | public LocalDate getDay(DayOfWeek dayOfWeek) { 30 | // DayOfWeek enum starts with monday == 1 31 | return days.get(dayOfWeek.getValue() - 1); 32 | } 33 | 34 | public Week nextWeek() { 35 | final LocalDate friday = getDay(DayOfWeek.FRIDAY); 36 | return new Week(friday.plusDays(3)); 37 | } 38 | 39 | public Week prevWeek() { 40 | final LocalDate monday = getDay(DayOfWeek.MONDAY); 41 | return new Week(monday.minusDays(3)); 42 | } 43 | 44 | public String toString() { 45 | return "Week of the " + getDay(DayOfWeek.MONDAY); 46 | } 47 | 48 | public static void main(String[] args) { 49 | LocalDate now = LocalDate.now(); 50 | Week currentWeek = new Week(now); 51 | System.out.println(currentWeek); 52 | System.out.println(currentWeek.prevWeek()); 53 | System.out.println(currentWeek.nextWeek()); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/com/davidmoodie/SwingCalendar/WeekCalendar.java: -------------------------------------------------------------------------------- 1 | package com.davidmoodie.SwingCalendar; 2 | 3 | import java.time.DayOfWeek; 4 | import java.time.LocalDate; 5 | import java.util.ArrayList; 6 | 7 | public class WeekCalendar extends Calendar { 8 | 9 | private Week week; 10 | 11 | public WeekCalendar(ArrayList events) { 12 | super(events); 13 | week = new Week(LocalDate.now()); 14 | } 15 | 16 | @Override 17 | protected boolean dateInRange(LocalDate date) { 18 | return Week.getStartOfWeek(date).equals(week.getDay(DayOfWeek.MONDAY)); 19 | } 20 | 21 | @Override 22 | protected LocalDate getDateFromDay(DayOfWeek day) { 23 | return week.getDay(day); 24 | } 25 | 26 | protected int numDaysToShow() { 27 | return 5; 28 | } 29 | 30 | @Override 31 | protected DayOfWeek getStartDay() { 32 | return DayOfWeek.MONDAY; 33 | } 34 | 35 | @Override 36 | protected DayOfWeek getEndDay() { 37 | return DayOfWeek.FRIDAY; 38 | } 39 | 40 | @Override 41 | protected void setRangeToToday() { 42 | week = new Week(LocalDate.now()); 43 | } 44 | 45 | @Override 46 | protected double dayToPixel(DayOfWeek dayOfWeek) { 47 | return TIME_COL_WIDTH + getDayWidth() * (dayOfWeek.getValue() - 1); 48 | } 49 | 50 | public void nextWeek() { 51 | week = week.nextWeek(); 52 | repaint(); 53 | } 54 | 55 | public void prevWeek() { 56 | week = week.prevWeek(); 57 | repaint(); 58 | } 59 | 60 | public void nextMonth() { 61 | week = new Week(week.getDay(DayOfWeek.MONDAY).plusWeeks(4)); 62 | repaint(); 63 | } 64 | 65 | public void prevMonth() { 66 | week = new Week(week.getDay(DayOfWeek.MONDAY).minusWeeks(4)); 67 | repaint(); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /test/DayCalendarTest.java: -------------------------------------------------------------------------------- 1 | import com.davidmoodie.SwingCalendar.Calendar; 2 | import com.davidmoodie.SwingCalendar.CalendarEvent; 3 | import com.davidmoodie.SwingCalendar.DayCalendar; 4 | 5 | import javax.swing.*; 6 | import java.awt.*; 7 | import java.time.LocalDate; 8 | import java.time.LocalTime; 9 | import java.util.ArrayList; 10 | 11 | public class DayCalendarTest { 12 | public static void main(String[] args) { 13 | JFrame frm = new JFrame(); 14 | 15 | ArrayList events = new ArrayList<>(); 16 | events.add(new CalendarEvent(LocalDate.of(2016, 11, 11), LocalTime.of(14, 0), LocalTime.of(14, 20), "Test 11/11 14:00-14:20")); 17 | events.add(new CalendarEvent(LocalDate.of(2016, 11, 14), LocalTime.of(9, 0), LocalTime.of(9, 20), "Test 14/11 9:00-9:20")); 18 | events.add(new CalendarEvent(LocalDate.of(2016, 11, 15), LocalTime.of(12, 0), LocalTime.of(13, 20), "Test 15/11 12:00-13:20")); 19 | events.add(new CalendarEvent(LocalDate.of(2016, 11, 16), LocalTime.of(9, 0), LocalTime.of(9, 20), "Test 16/11 9:00-9:20")); 20 | events.add(new CalendarEvent(LocalDate.of(2016, 11, 17), LocalTime.of(12, 15), LocalTime.of(14, 20), "Test 17/11 12:15-14:20")); 21 | events.add(new CalendarEvent(LocalDate.of(2016, 11, 18), LocalTime.of(9, 30), LocalTime.of(10, 00), "Test 18/11 9:30-10:00")); 22 | events.add(new CalendarEvent(LocalDate.of(2016, 11, 18), LocalTime.of(16, 00), LocalTime.of(16, 45), "Test 18/11 16:00-16:45")); 23 | 24 | DayCalendar cal = new DayCalendar(events); 25 | 26 | cal.addCalendarEventClickListener(e -> System.out.println(e.getCalendarEvent())); 27 | cal.addCalendarEmptyClickListener(e -> { 28 | System.out.println(e.getDateTime()); 29 | System.out.println(Calendar.roundTime(e.getDateTime().toLocalTime(), 30)); 30 | }); 31 | 32 | JButton goToTodayBtn = new JButton("Today"); 33 | goToTodayBtn.addActionListener(e -> cal.goToToday()); 34 | 35 | JButton nextDayBtn = new JButton(">"); 36 | nextDayBtn.addActionListener(e -> cal.nextDay()); 37 | 38 | JButton prevDayBtn = new JButton("<"); 39 | prevDayBtn.addActionListener(e -> cal.prevDay()); 40 | 41 | JPanel weekControls = new JPanel(); 42 | weekControls.add(prevDayBtn); 43 | weekControls.add(goToTodayBtn); 44 | weekControls.add(nextDayBtn); 45 | 46 | frm.add(weekControls, BorderLayout.NORTH); 47 | 48 | frm.add(cal, BorderLayout.CENTER); 49 | frm.setSize(1000, 900); 50 | frm.setVisible(true); 51 | frm.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /test/WeekCalendarTest.java: -------------------------------------------------------------------------------- 1 | import com.davidmoodie.SwingCalendar.Calendar; 2 | import com.davidmoodie.SwingCalendar.CalendarEvent; 3 | import com.davidmoodie.SwingCalendar.WeekCalendar; 4 | 5 | import javax.swing.*; 6 | import java.awt.*; 7 | import java.time.LocalDate; 8 | import java.time.LocalTime; 9 | import java.util.ArrayList; 10 | 11 | public class WeekCalendarTest { 12 | public static void main(String[] args) { 13 | JFrame frm = new JFrame(); 14 | 15 | ArrayList events = new ArrayList<>(); 16 | events.add(new CalendarEvent(LocalDate.of(2016, 11, 11), LocalTime.of(14, 0), LocalTime.of(14, 20), "Test 11/11 14:00-14:20")); 17 | events.add(new CalendarEvent(LocalDate.of(2016, 11, 14), LocalTime.of(9, 0), LocalTime.of(9, 20), "Test 14/11 9:00-9:20")); 18 | events.add(new CalendarEvent(LocalDate.of(2016, 11, 15), LocalTime.of(12, 0), LocalTime.of(13, 20), "Test 15/11 12:00-13:20")); 19 | events.add(new CalendarEvent(LocalDate.of(2016, 11, 16), LocalTime.of(9, 0), LocalTime.of(9, 20), "Test 16/11 9:00-9:20")); 20 | events.add(new CalendarEvent(LocalDate.of(2016, 11, 17), LocalTime.of(12, 15), LocalTime.of(14, 20), "Test 17/11 12:15-14:20")); 21 | events.add(new CalendarEvent(LocalDate.of(2016, 11, 18), LocalTime.of(9, 30), LocalTime.of(10, 00), "Test 18/11 9:30-10:00")); 22 | events.add(new CalendarEvent(LocalDate.of(2016, 11, 18), LocalTime.of(16, 00), LocalTime.of(16, 45), "Test 18/11 16:00-16:45")); 23 | 24 | WeekCalendar cal = new WeekCalendar(events); 25 | 26 | cal.addCalendarEventClickListener(e -> System.out.println(e.getCalendarEvent())); 27 | cal.addCalendarEmptyClickListener(e -> { 28 | System.out.println(e.getDateTime()); 29 | System.out.println(Calendar.roundTime(e.getDateTime().toLocalTime(), 30)); 30 | }); 31 | 32 | JButton goToTodayBtn = new JButton("Today"); 33 | goToTodayBtn.addActionListener(e -> cal.goToToday()); 34 | 35 | JButton nextWeekBtn = new JButton(">"); 36 | nextWeekBtn.addActionListener(e -> cal.nextWeek()); 37 | 38 | JButton prevWeekBtn = new JButton("<"); 39 | prevWeekBtn.addActionListener(e -> cal.prevWeek()); 40 | 41 | JButton nextMonthBtn = new JButton(">>"); 42 | nextMonthBtn.addActionListener(e -> cal.nextMonth()); 43 | 44 | JButton prevMonthBtn = new JButton("<<"); 45 | prevMonthBtn.addActionListener(e -> cal.prevMonth()); 46 | 47 | JPanel weekControls = new JPanel(); 48 | weekControls.add(prevMonthBtn); 49 | weekControls.add(prevWeekBtn); 50 | weekControls.add(goToTodayBtn); 51 | weekControls.add(nextWeekBtn); 52 | weekControls.add(nextMonthBtn); 53 | 54 | frm.add(weekControls, BorderLayout.NORTH); 55 | 56 | frm.add(cal, BorderLayout.CENTER); 57 | frm.setSize(1000, 900); 58 | frm.setVisible(true); 59 | frm.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); 60 | } 61 | } 62 | --------------------------------------------------------------------------------