/*
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.optaplanner.examples.meetingscheduling.swingui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.SwingConstants;
import org.apache.commons.lang3.tuple.Pair;
import org.optaplanner.examples.common.swingui.SolutionPanel;
import org.optaplanner.examples.common.swingui.timetable.TimeTablePanel;
import org.optaplanner.examples.meetingscheduling.domain.Day;
import org.optaplanner.examples.meetingscheduling.domain.MeetingAssignment;
import org.optaplanner.examples.meetingscheduling.domain.MeetingSchedule;
import org.optaplanner.examples.meetingscheduling.domain.Person;
import org.optaplanner.examples.meetingscheduling.domain.PreferredAttendance;
import org.optaplanner.examples.meetingscheduling.domain.RequiredAttendance;
import org.optaplanner.examples.meetingscheduling.domain.Room;
import org.optaplanner.examples.meetingscheduling.domain.TimeGrain;
import org.optaplanner.swing.impl.SwingUtils;
import org.optaplanner.swing.impl.TangoColorFactory;
import static org.apache.commons.lang3.ObjectUtils.*;
import static org.optaplanner.examples.common.swingui.timetable.TimeTablePanel.HeaderColumnKey.*;
import static org.optaplanner.examples.common.swingui.timetable.TimeTablePanel.HeaderRowKey.*;
public class MeetingSchedulingPanel extends SolutionPanel<MeetingSchedule> {
public static final String LOGO_PATH = "/org/optaplanner/examples/meetingscheduling/swingui/meetingSchedulingLogo.png";
private final TimeTablePanel<TimeGrain, Room> roomsPanel;
private final TimeTablePanel<TimeGrain, Pair<Person, Boolean>> personsPanel;
private final OvertimeTimeGrain OVERTIME_TIME_GRAIN = new OvertimeTimeGrain();
public MeetingSchedulingPanel() {
setLayout(new BorderLayout());
JTabbedPane tabbedPane = new JTabbedPane();
roomsPanel = new TimeTablePanel<>();
tabbedPane.add("Rooms", new JScrollPane(roomsPanel));
personsPanel = new TimeTablePanel<>();
tabbedPane.add("Persons", new JScrollPane(personsPanel));
add(tabbedPane, BorderLayout.CENTER);
setPreferredSize(PREFERRED_SCROLLABLE_VIEWPORT_SIZE);
}
@Override
public boolean isWrapInScrollPane() {
return false;
}
@Override
public void resetPanel(MeetingSchedule meetingSchedule) {
roomsPanel.reset();
personsPanel.reset();
defineGrid(meetingSchedule);
fillCells(meetingSchedule);
repaint(); // Hack to force a repaint of TimeTableLayout during "refresh screen while solving"
}
private void defineGrid(MeetingSchedule meetingSchedule) {
roomsPanel.defineColumnHeaderByKey(HEADER_COLUMN); // Room header
personsPanel.defineColumnHeaderByKey(HEADER_COLUMN_GROUP1); // Person header
personsPanel.defineColumnHeaderByKey(HEADER_COLUMN); // Required header
for (TimeGrain timeGrain : meetingSchedule.getTimeGrainList()) {
roomsPanel.defineColumnHeader(timeGrain);
personsPanel.defineColumnHeader(timeGrain);
}
roomsPanel.defineColumnHeader(OVERTIME_TIME_GRAIN); // Overtime timeGrain
personsPanel.defineColumnHeader(OVERTIME_TIME_GRAIN); // Overtime timeGrain
roomsPanel.defineColumnHeader(null); // Unassigned timeGrain
personsPanel.defineColumnHeader(null); // Unassigned timeGrain
roomsPanel.defineRowHeaderByKey(HEADER_ROW_GROUP1); // Date header
roomsPanel.defineRowHeaderByKey(HEADER_ROW); // TimeGrain header
for (Room room : meetingSchedule.getRoomList()) {
roomsPanel.defineRowHeader(room);
}
roomsPanel.defineRowHeader(null); // Unassigned
personsPanel.defineRowHeaderByKey(HEADER_ROW_GROUP1); // Day header
personsPanel.defineRowHeaderByKey(HEADER_ROW); // TimeGrain header
for (Person person : meetingSchedule.getPersonList()) {
personsPanel.defineRowHeader(Pair.of(person, Boolean.TRUE));
personsPanel.defineRowHeader(Pair.of(person, Boolean.FALSE));
}
}
private void fillCells(MeetingSchedule meetingSchedule) {
roomsPanel.addCornerHeader(HEADER_COLUMN, HEADER_ROW, createTableHeader(new JLabel("Room")));
fillRoomCells(meetingSchedule);
personsPanel.addCornerHeader(HEADER_COLUMN_GROUP1, HEADER_ROW, createTableHeader(new JLabel("Person")));
personsPanel.addCornerHeader(HEADER_COLUMN, HEADER_ROW, createTableHeader(new JLabel("Attendance")));
fillPersonCells(meetingSchedule);
fillTimeGrainCells(meetingSchedule);
fillMeetingAssignmentCells(meetingSchedule);
}
private void fillRoomCells(MeetingSchedule meetingSchedule) {
for (Room room : meetingSchedule.getRoomList()) {
roomsPanel.addRowHeader(HEADER_COLUMN, room,
createTableHeader(new JLabel(room.getLabel(), SwingConstants.CENTER)));
}
roomsPanel.addRowHeader(HEADER_COLUMN, null,
createTableHeader(new JLabel("Unassigned", SwingConstants.CENTER)));
}
private void fillPersonCells(MeetingSchedule meetingSchedule) {
for (Person person : meetingSchedule.getPersonList()) {
personsPanel.addRowHeader(HEADER_COLUMN_GROUP1, Pair.of(person, Boolean.TRUE),
HEADER_COLUMN_GROUP1, Pair.of(person, Boolean.FALSE),
createTableHeader(new JLabel(person.getLabel(), SwingConstants.CENTER)));
personsPanel.addRowHeader(HEADER_COLUMN, Pair.of(person, Boolean.TRUE),
createTableHeader(new JLabel("Required", SwingConstants.CENTER)));
personsPanel.addRowHeader(HEADER_COLUMN, Pair.of(person, Boolean.FALSE),
createTableHeader(new JLabel("Preferred", SwingConstants.CENTER)));
}
}
private void fillTimeGrainCells(MeetingSchedule meetingSchedule) {
Map<Day, TimeGrain> firstTimeGrainMap = new HashMap<>(meetingSchedule.getDayList().size());
Map<Day, TimeGrain> lastTimeGrainMap = new HashMap<>(meetingSchedule.getDayList().size());
for (TimeGrain timeGrain : meetingSchedule.getTimeGrainList()) {
Day day = timeGrain.getDay();
TimeGrain firstTimeGrain = firstTimeGrainMap.get(day);
if (firstTimeGrain == null || firstTimeGrain.getGrainIndex() > timeGrain.getGrainIndex()) {
firstTimeGrainMap.put(day, timeGrain);
}
TimeGrain lastTimeGrain = lastTimeGrainMap.get(day);
if (lastTimeGrain == null || lastTimeGrain.getGrainIndex() < timeGrain.getGrainIndex()) {
lastTimeGrainMap.put(day, timeGrain);
}
roomsPanel.addColumnHeader(timeGrain, HEADER_ROW,
createTableHeader(new JLabel(timeGrain.getLabel())));
personsPanel.addColumnHeader(timeGrain, HEADER_ROW,
createTableHeader(new JLabel(timeGrain.getLabel())));
}
roomsPanel.addColumnHeader(OVERTIME_TIME_GRAIN, HEADER_ROW,
createTableHeader(new JLabel("Overtime")));
personsPanel.addColumnHeader(OVERTIME_TIME_GRAIN, HEADER_ROW,
createTableHeader(new JLabel("Overtime")));
roomsPanel.addColumnHeader(null, HEADER_ROW,
createTableHeader(new JLabel("Unassigned")));
personsPanel.addColumnHeader(null, HEADER_ROW,
createTableHeader(new JLabel("Unassigned")));
for (Day day : meetingSchedule.getDayList()) {
TimeGrain firstTimeGrain = firstTimeGrainMap.get(day);
TimeGrain lastTimeGrain = lastTimeGrainMap.get(day);
roomsPanel.addColumnHeader(firstTimeGrain, HEADER_ROW_GROUP1, lastTimeGrain, HEADER_ROW_GROUP1,
createTableHeader(new JLabel(day.getLabel())));
personsPanel.addColumnHeader(firstTimeGrain, HEADER_ROW_GROUP1, lastTimeGrain, HEADER_ROW_GROUP1,
createTableHeader(new JLabel(day.getLabel())));
}
}
private void fillMeetingAssignmentCells(MeetingSchedule meetingSchedule) {
TangoColorFactory tangoColorFactory = new TangoColorFactory();
for (MeetingAssignment meetingAssignment : meetingSchedule.getMeetingAssignmentList()) {
Color color = tangoColorFactory.pickColor(meetingAssignment.getMeeting());
TimeGrain startingTimeGrain = meetingAssignment.getStartingTimeGrain();
TimeGrain lastTimeGrain;
if (startingTimeGrain == null) {
lastTimeGrain = null;
} else {
int lastTimeGrainIndex = meetingAssignment.getLastTimeGrainIndex();
List<TimeGrain> timeGrainList = meetingSchedule.getTimeGrainList();
if (lastTimeGrainIndex < meetingSchedule.getTimeGrainList().size()) {
lastTimeGrain = timeGrainList.get(lastTimeGrainIndex);
} else {
lastTimeGrain = OVERTIME_TIME_GRAIN;
}
}
roomsPanel.addCell(startingTimeGrain, meetingAssignment.getRoom(),
lastTimeGrain, meetingAssignment.getRoom(),
createButton(meetingAssignment, color));
for (RequiredAttendance requiredAttendance : meetingAssignment.getMeeting().getRequiredAttendanceList()) {
Pair<Person, Boolean> pair = Pair.of(requiredAttendance.getPerson(), Boolean.TRUE);
personsPanel.addCell(startingTimeGrain, pair,
lastTimeGrain, pair,
createButton(meetingAssignment, color));
}
for (PreferredAttendance preferredAttendance : meetingAssignment.getMeeting().getPreferredAttendanceList()) {
Pair<Person, Boolean> pair = Pair.of(preferredAttendance.getPerson(), Boolean.FALSE);
personsPanel.addCell(startingTimeGrain, pair,
lastTimeGrain, pair,
createButton(meetingAssignment, color));
}
}
}
private JPanel createTableHeader(JLabel label) {
JPanel headerPanel = new JPanel(new BorderLayout());
headerPanel.add(label, BorderLayout.NORTH);
headerPanel.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createLineBorder(TangoColorFactory.ALUMINIUM_5),
BorderFactory.createEmptyBorder(2, 2, 2, 2)));
return headerPanel;
}
private JButton createButton(MeetingAssignment meetingAssignment, Color color) {
JButton button = SwingUtils.makeSmallButton(new JButton(new MeetingAssignmentAction(meetingAssignment)));
button.setBackground(color);
return button;
}
private class MeetingAssignmentAction extends AbstractAction {
private MeetingAssignment meetingAssignment;
public MeetingAssignmentAction(MeetingAssignment meetingAssignment) {
super(meetingAssignment.getLabel());
putValue(SHORT_DESCRIPTION, "<html>Topic: " + meetingAssignment.getMeeting().getTopic() + "<br/>"
+ "Date and time: " + defaultIfNull(meetingAssignment.getStartingDateTimeString(), "unassigned") + "<br/>"
+ "Duration: " + meetingAssignment.getMeeting().getDurationString() + "<br/>"
+ "Room: " + defaultIfNull(meetingAssignment.getRoom(), "unassigned")
+ "</html>");
this.meetingAssignment = meetingAssignment;
}
@Override
public void actionPerformed(ActionEvent e) {
// TODO Add support for moving meetings like in other examples
JOptionPane.showMessageDialog(MeetingSchedulingPanel.this.getTopLevelAncestor(),
"The GUI does not support this action yet.",
"Unsupported in GUI", JOptionPane.ERROR_MESSAGE);
}
}
private static final class OvertimeTimeGrain extends TimeGrain {
private OvertimeTimeGrain() {
setGrainIndex(-1);
setDay(null);
setStartingMinuteOfDay(-1);
}
@Override
public String getDateTimeString() {
return "Overtime";
}
}
}