/*
* Copyright 2010 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.examination.swingui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComboBox;
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.optaplanner.examples.common.swingui.SolutionPanel;
import org.optaplanner.examples.common.swingui.SolverAndPersistenceFrame;
import org.optaplanner.examples.common.swingui.components.LabeledComboBoxRenderer;
import org.optaplanner.examples.common.swingui.timetable.TimeTablePanel;
import org.optaplanner.examples.examination.domain.Exam;
import org.optaplanner.examples.examination.domain.Examination;
import org.optaplanner.examples.examination.domain.FollowingExam;
import org.optaplanner.examples.examination.domain.Period;
import org.optaplanner.examples.examination.domain.Room;
import org.optaplanner.swing.impl.SwingUtils;
import org.optaplanner.swing.impl.TangoColorFactory;
import static org.optaplanner.examples.common.swingui.timetable.TimeTablePanel.HeaderColumnKey.*;
import static org.optaplanner.examples.common.swingui.timetable.TimeTablePanel.HeaderRowKey.*;
public class ExaminationPanel extends SolutionPanel<Examination> {
public static final String LOGO_PATH = "/org/optaplanner/examples/examination/swingui/examinationLogo.png";
private final TimeTablePanel<Room, Period> roomsPanel;
private InstitutionParametrizationDialog institutionParametrizationDialog;
private AbstractAction institutionParametrizationEditAction;
private int maximumPeriodDuration;
private int maximumRoomCapacity;
public ExaminationPanel() {
setLayout(new BorderLayout());
JTabbedPane tabbedPane = new JTabbedPane();
roomsPanel = new TimeTablePanel<>();
tabbedPane.add("Rooms", new JScrollPane(roomsPanel));
add(tabbedPane, BorderLayout.CENTER);
add(createFooterPanel(), BorderLayout.SOUTH);
setPreferredSize(PREFERRED_SCROLLABLE_VIEWPORT_SIZE);
}
private JPanel createFooterPanel() {
JPanel footerPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
institutionParametrizationEditAction = new AbstractAction("Edit scoring parameters") {
@Override
public void actionPerformed(ActionEvent e) {
if (solutionBusiness.isSolving()) {
JOptionPane.showMessageDialog(ExaminationPanel.this.getTopLevelAncestor(),
"The GUI does not support this action yet during solving.\n"
+ "OptaPlanner itself does support it.\n"
+ "\nTerminate solving first and try again.",
"Unsupported in GUI", JOptionPane.ERROR_MESSAGE);
return;
}
institutionParametrizationDialog.setInstitutionParametrization(
getSolution().getInstitutionParametrization());
institutionParametrizationDialog.setVisible(true);
}
};
institutionParametrizationEditAction.setEnabled(false);
footerPanel.add(new JButton(institutionParametrizationEditAction));
return footerPanel;
}
@Override
public void setSolverAndPersistenceFrame(SolverAndPersistenceFrame solverAndPersistenceFrame) {
super.setSolverAndPersistenceFrame(solverAndPersistenceFrame);
institutionParametrizationDialog = new InstitutionParametrizationDialog(solverAndPersistenceFrame, this);
}
@Override
public boolean isWrapInScrollPane() {
return false;
}
@Override
public void resetPanel(Examination examination) {
roomsPanel.reset();
refreshMaximums(examination);
defineGrid(examination);
fillCells(examination);
institutionParametrizationEditAction.setEnabled(true);
repaint(); // Hack to force a repaint of TimeTableLayout during "refresh screen while solving"
}
private void refreshMaximums(Examination examination) {
maximumPeriodDuration = 0;
for (Period period : examination.getPeriodList()) {
int periodDuration = period.getDuration();
if (periodDuration > maximumPeriodDuration) {
maximumPeriodDuration = periodDuration;
}
}
maximumRoomCapacity = 0;
for (Room room : examination.getRoomList()) {
int roomCapacity = room.getCapacity();
if (roomCapacity > maximumRoomCapacity) {
maximumRoomCapacity = roomCapacity;
}
}
}
private void defineGrid(Examination examination) {
JButton footprint = SwingUtils.makeSmallButton(new JButton("99999999"));
int footprintWidth = footprint.getPreferredSize().width;
roomsPanel.defineColumnHeaderByKey(HEADER_COLUMN); // Period header
for (Room room : examination.getRoomList()) {
roomsPanel.defineColumnHeader(room, footprintWidth);
}
roomsPanel.defineColumnHeader(null, footprintWidth); // Unassigned
roomsPanel.defineRowHeaderByKey(HEADER_ROW); // Room header
for (Period period : examination.getPeriodList()) {
roomsPanel.defineRowHeader(period);
}
roomsPanel.defineRowHeader(null); // Unassigned period
}
private void fillCells(Examination examination) {
roomsPanel.addCornerHeader(HEADER_COLUMN, HEADER_ROW, createTableHeader(new JLabel("Time"), null));
fillRoomCells(examination);
fillPeriodCells(examination);
fillExamCells(examination);
}
private void fillRoomCells(Examination examination) {
for (Room room : examination.getRoomList()) {
roomsPanel.addColumnHeader(room, HEADER_ROW,
createTableHeader(new JLabel(room.getLabel(), new RoomIcon(room), SwingConstants.CENTER),
"Capacity: " + room.getCapacity() + " (shown as rectangle)"));
}
roomsPanel.addColumnHeader(null, HEADER_ROW,
createTableHeader(new JLabel("Unassigned", SwingConstants.CENTER), null));
}
private void fillPeriodCells(Examination examination) {
for (Period period : examination.getPeriodList()) {
roomsPanel.addRowHeader(HEADER_COLUMN, period, createTableHeader(
new JLabel(period.getLabel(), new PeriodIcon(period), SwingConstants.LEFT),
"Duration: " + period.getDuration()));
}
roomsPanel.addRowHeader(HEADER_COLUMN, null,
createTableHeader(new JLabel("Unassigned"), null));
}
private void fillExamCells(Examination examination) {
preparePlanningEntityColors(examination.getExamList());
for (Exam exam : examination.getExamList()) {
Color color = determinePlanningEntityColor(exam, exam);
String toolTip = determinePlanningEntityTooltip(exam);
roomsPanel.addCell(exam.getRoom(), exam.getPeriod(),
createButton(exam, color, toolTip));
}
}
private JPanel createTableHeader(JLabel label, String toolTip) {
if (toolTip != null) {
label.setToolTipText(toolTip);
}
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(Exam exam, Color color, String toolTip) {
JButton button = SwingUtils.makeSmallButton(new JButton(new ExamAction(exam)));
button.setBackground(color);
button.setToolTipText(toolTip);
if (exam instanceof FollowingExam) {
button.setForeground(TangoColorFactory.ALUMINIUM_5);
}
return button;
}
@Override
public boolean isIndictmentHeatMapEnabled() {
return true;
}
private class ExamAction extends AbstractAction {
private Exam exam;
public ExamAction(Exam exam) {
super(exam.getLabel(), new ExamIcon(exam));
this.exam = exam;
// Tooltip
putValue(SHORT_DESCRIPTION, "<html>Exam: " + exam.getLabel() + "<br/>"
+ "Duration: " + exam.getTopicDuration() + " (shown in circle)<br/>"
+ "Has " + exam.getTopicStudentSize() + " students (shown in rectangle)"
+ "</html>");
}
@Override
public void actionPerformed(ActionEvent e) {
JPanel listFieldsPanel = new JPanel(new GridLayout(2, 2));
listFieldsPanel.add(new JLabel("Period:"));
Examination examination = getSolution();
List<Period> periodList = examination.getPeriodList();
// Add 1 to array size to add null, which makes the entity unassigned
JComboBox periodListField = new JComboBox(
periodList.toArray(new Object[periodList.size() + 1]));
LabeledComboBoxRenderer.applyToComboBox(periodListField);
periodListField.setSelectedItem(exam.getPeriod());
listFieldsPanel.add(periodListField);
listFieldsPanel.add(new JLabel("Room:"));
List<Room> roomList = examination.getRoomList();
// Add 1 to array size to add null, which makes the entity unassigned
JComboBox roomListField = new JComboBox(
roomList.toArray(new Object[roomList.size() + 1]));
LabeledComboBoxRenderer.applyToComboBox(roomListField);
roomListField.setSelectedItem(exam.getRoom());
listFieldsPanel.add(roomListField);
int result = JOptionPane.showConfirmDialog(ExaminationPanel.this.getRootPane(), listFieldsPanel,
"Select period and room", JOptionPane.OK_CANCEL_OPTION);
if (result == JOptionPane.OK_OPTION) {
Period toPeriod = (Period) periodListField.getSelectedItem();
solutionBusiness.doChangeMove(exam, "period", toPeriod);
Room toRoom = (Room) roomListField.getSelectedItem();
solutionBusiness.doChangeMove(exam, "room", toRoom);
solverAndPersistenceFrame.resetScreen();
}
}
}
private class PeriodIcon implements Icon {
private static final int DIAMETER = 14;
private final Period period;
private PeriodIcon(Period period) {
this.period = period;
}
@Override
public int getIconWidth() {
return DIAMETER;
}
@Override
public int getIconHeight() {
return DIAMETER;
}
@Override
public void paintIcon(Component c, Graphics g, int x, int y) {
g.setColor(TangoColorFactory.ALUMINIUM_6);
g.fillOval(x, y, DIAMETER, DIAMETER);
g.setColor(Color.WHITE);
g.fillArc(x, y, DIAMETER, DIAMETER, 90, -(360 * period.getDuration() / maximumPeriodDuration));
g.setColor(TangoColorFactory.ALUMINIUM_6);
g.drawOval(x, y, DIAMETER, DIAMETER);
}
}
private class RoomIcon implements Icon {
private static final int ICON_WIDTH = 10;
private static final int ICON_HEIGHT = PeriodIcon.DIAMETER;
private final Room room;
private RoomIcon(Room room) {
this.room = room;
}
@Override
public int getIconWidth() {
return ICON_WIDTH;
}
@Override
public int getIconHeight() {
return ICON_HEIGHT;
}
@Override
public void paintIcon(Component c, Graphics g, int x, int y) {
g.setColor(TangoColorFactory.ALUMINIUM_6);
g.fillRect(x + 1, y, ICON_WIDTH - 2, ICON_HEIGHT);
g.setColor(Color.WHITE);
int capacityHeight = ICON_HEIGHT * room.getCapacity() / maximumRoomCapacity;
g.fillRect(x + 1, y + (ICON_HEIGHT - capacityHeight), ICON_WIDTH - 2, capacityHeight);
g.setColor(TangoColorFactory.ALUMINIUM_6);
g.drawRect(x + 1, y, ICON_WIDTH - 2, ICON_HEIGHT);
}
}
private class ExamIcon implements Icon {
private static final int DIAMETER = PeriodIcon.DIAMETER;
private static final int ICON_WIDTH = RoomIcon.ICON_WIDTH;
private static final int ICON_HEIGHT = RoomIcon.ICON_HEIGHT;
private final Exam exam;
private ExamIcon(Exam exam) {
this.exam = exam;
}
@Override
public int getIconWidth() {
return DIAMETER + ICON_WIDTH;
}
@Override
public int getIconHeight() {
return DIAMETER;
}
@Override
public void paintIcon(Component c, Graphics g, int x, int y) {
g.setColor(Color.WHITE);
g.fillOval(x, y, DIAMETER, DIAMETER);
g.setColor(TangoColorFactory.ALUMINIUM_4);
g.fillArc(x, y, DIAMETER, DIAMETER, 90, -(360 * exam.getTopicDuration() / maximumPeriodDuration));
g.setColor(TangoColorFactory.ALUMINIUM_6);
g.drawOval(x, y, DIAMETER, DIAMETER);
x += DIAMETER + 1;
g.setColor(Color.WHITE);
g.fillRect(x + 1, y, ICON_WIDTH - 2, ICON_HEIGHT);
g.setColor(TangoColorFactory.ALUMINIUM_4);
int capacityHeight = ICON_HEIGHT * exam.getTopicStudentSize() / maximumRoomCapacity;
g.fillRect(x + 1, y + (ICON_HEIGHT - capacityHeight), ICON_WIDTH - 2, capacityHeight);
g.setColor(TangoColorFactory.ALUMINIUM_6);
g.drawRect(x + 1, y, ICON_WIDTH - 2, ICON_HEIGHT);
}
}
}