package kakuro.gui.editor;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.HashMap;
import javax.swing.JPanel;
import kakuro.server.ISkeletonTable;
import kakuro.table.ITable;
import kakuro.table.Position;
import kakuro.table.TableException;
/** Skeleton szerkesztő panel. Nem négyzet alakú, azt a szerkesztő ablak biztosítja megfelelő layout-tal! */
public class SkeletonPanel extends JPanel {
/** Szerkesztő ablak */
private SkeletonEditor _editor = null;
/** oszlopok száma */
private int _x = 4;
/** sorok száma */
private int _y = 4;
/** nehézségi szint */
private int _difficulty = 0;
/** Fekete mezőket tartalmazó map */
private HashMap<Position, Boolean> _blacks = new HashMap<Position, Boolean>();
/** Összes cella, a grafikus megjelenítésért felelős SkellyField-ek */
private HashMap<Position, SkellyField> _fields = new HashMap<Position, SkellyField>();
/** fel van-e töltve a tábla számokkal is? */
private boolean showValues = false;
/** Reagál-e a tábla az egéreseményekre. Ha false, akkor preview-ként funkcionál */
private boolean _readOnly = false;
/** Utolsóként betöltött tábla-modell. Innen kapja a számjegyeket a szerkesztő. */
private ITable _lastTable = null;
/** Problémás mezők száma (túl hosszú szavak a gond). */
private int problematicCounter = 0;
/** Gyorsításra szolgáló Map, a kirajzolásra használt fontokat tároljuk itt. */
private static final HashMap<Integer, Font> _fontMap = new HashMap<Integer, Font>();
/** Előre legeneráljuk a használt fontokat, mert ez elég időigényes tábla átméretezése közben. */
static {
Font font = new Font("Monospaced", Font.BOLD, 12);
_fontMap.put(12, font);
for (int i=13; i<48; i++) _fontMap.put(i, font.deriveFont((float)i));
}
/** Alap konstruktor
*
* @param editor szerkesztő ablak
* @param readOnly csak olvasható (true: preview ablakként működik)
* */
public SkeletonPanel(SkeletonEditor editor, boolean readOnly) {
_editor = editor;
_readOnly = readOnly;
setOpaque(false);
}
/** Hibamentes-e a skeleton - akkor hibamentes, ha nincs benne 9-nél hosszabb szó. */
public boolean isCorrect() {
System.out.println("problematicCounter: "+problematicCounter);
return problematicCounter==0;
}
/** Minimum méret
*
* @return fixen 100, 100
* */
public Dimension getMinimumSize() {
return new Dimension(100, 100);
}
/** Preferált méret, fix érték, a layout manager úgyis beméretezi majd helyesen.
*
* @return fix 500, 500
*/
public Dimension getPreferredSize() {
return new Dimension(500, 500);
}
/** Skeleton betöltése a panelbe.
*
* @param skeleton fekete-fehér tábla
* */
public void load(ISkeletonTable skeleton) {
_x = skeleton.getColumnCount();
_y = skeleton.getRowCount();
_difficulty = skeleton.getDifficulty();
_blacks.clear();
_fields.clear();
for (int i=0; i<_x; i++)
for (int j=0; j<_y; j++)
if (skeleton.getType(new Position(i, j))==ISkeletonTable.CELL_BLACK)
_blacks.put(new Position(i, j), true);
redraw();
}
/** Panel újraépítése a nulláról, összes gyereket újragyártja a {@link #_blacks} tábla alapján,
* panelt újraméretezi, hosszú szavakat leellenőrzi, layout-ot újraépíti. */
protected void redraw() {
removeAll();
setPreferredSize(new Dimension(_x * 80, _y * 80));
setMinimumSize(new Dimension(_x * 40, _y * 40));
GridLayout layout = new GridLayout(_y, _x, 1, 1);
setLayout(layout);
for (int j = 0; j < _y; j++) {
for (int i = 0; i < _x; i++) {
Position pos = new Position(i, j);
SkellyField f = new SkellyField(pos, _blacks.containsKey(pos));
add(f);
_fields.put(pos, f);
}
}
for (int j = 0; j < _y; j++) {
for (int i = 0; i < _x; i++) {
Position pos = new Position(i, j);
checkLongs(pos);
}
}
validate();
repaint();
}
/** Az adott pozíció sorára és oszlopára leellenőrzi, hogy vannak-e túl hosszú szavak.
*
* @param p kiindulási pozíció
* */
protected void checkLongs(Position p) {
for (int i=0; i<_x; i++) {
for (int j=0; j<_y; j++) {
if (i==p.x || j==p.y) checkLongs2(new Position(i, j));
}
}
}
/** A kapott pozíciótól jobbra-balra-lefele-felfele megkeresi az első fekete cellákat, és leellenőrzi, hogy
* túl hosszúak-e ezek a szavak. Ha túl hosszú, akkor a paraméterként kapott pozíciót problémásnak jelöli.
*
* @param pos kiindulási pozíció
* */
protected void checkLongs2(Position pos) {
if (_blacks.containsKey(pos)) {
_fields.get(pos).setProblematic(false);
return;
}
Position p;
int horizontalCount = 1;
for (int i=pos.x-1; i>=0; i--) {
p = new Position(i, pos.y);
if (_blacks.containsKey(p)) break;
horizontalCount++;
}
for (int i=pos.x+1; i<_x; i++) {
p = new Position(i, pos.y);
if (_blacks.containsKey(p)) break;
horizontalCount++;
}
int verticalCount = 1;
for (int i=pos.y-1; i>=0; i--) {
p = new Position(pos.x, i);
if (_blacks.containsKey(p)) break;
verticalCount++;
}
for (int i=pos.y+1; i<_y; i++) {
p = new Position(pos.x, i);
if (_blacks.containsKey(p)) break;
verticalCount++;
}
if (horizontalCount>9 || verticalCount>9) {
_fields.get(pos).setProblematic(true);
} else {
_fields.get(pos).setProblematic(false);
}
}
/** Belső osztály, egy skeleton cella megjelenítése a feladata. Egéreseményeket is kezeli. */
private class SkellyField extends JPanel {
/** Fekete a cella? */
public boolean isBlack = false;
/** Fehér cellában szám, fekete cellában első összeg */
int value1 = 0;
/** fekete cellában második összeg */
int value2 = 0;
/** használt font */
Font font = null;
/** cella helye */
Position _position = null;
/** problémás a cella? */
boolean problematic = false;
/** Alap konstruktor. Ha nem readonly a panel, akkor beregisztrálja az egéresemény-kezelőket is.
*
* @param p cella helye
* @param black fekete a cella?
* */
public SkellyField(Position p, boolean black) {
super();
isBlack = black;
setPreferredSize(new Dimension(80, 80));
setMinimumSize(new Dimension(40, 40));
_position = p;
refreshStatus();
refresh();
if (!_readOnly && p.x!=0 && p.y!=0) addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
super.mousePressed(e);
isBlack ^= true;
refreshStatus();
checkLongs(_position);
refresh();
}
public void mouseEntered(MouseEvent e) {
super.mouseEntered(e);
if ((e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK)==MouseEvent.BUTTON1_DOWN_MASK) {
isBlack ^= true;
refreshStatus();
checkLongs(_position);
refresh();
}
}
});
}
/** Szinkronizálja a cella saját fekete státuszát a panel fekete cellákat tartalmazó
* {@link SkeletonPanel#_blacks} Map tartalmával. A saját státusza az irányadó! */
protected void refreshStatus() {
if (!isBlack && _blacks.containsKey(_position)) {
_blacks.remove(_position);
}
if (isBlack && !_blacks.containsKey(_position)) {
_blacks.put(_position, true);
}
}
/** Cellát (nem) problémásnak jelöli meg, a kapott paramétertől függően.
*
* @param p problémás?
* */
public void setProblematic(boolean p) {
if (problematic && !p) {
problematic = p;
refresh();
problematicCounter--;
} else if (!problematic && p) {
problematic = p;
refresh();
problematicCounter++;
}
}
/** Hátteret újrarajzolja */
protected void refresh() {
if (isBlack) setBackground(Color.BLACK);
else {
if (!problematic) setBackground(Color.WHITE);
else setBackground(Color.YELLOW);
}
}
/** Számértékek, összegek beállítása
*
* @param i első összeg, vagy a fehér mező értéke
* @param j második összeg
* */
public void setValues(int i, int j) {
value1 = i;
value2 = j;
}
/** Felüldefiniált paint, az ős paint-je után rárajzolja a panelre a számértékeket is.
* A számok méretét a cella méretéből próbálja kiszámolni.
*
* @param g grafika
* */
public void paint(Graphics g) {
super.paint(g);
if (!showValues) return;
Graphics2D g2d = (Graphics2D)g;
if (font==null) {
int h = getHeight()/4;
if (h<12) h=12;
synchronized (_fontMap) {
if (!_fontMap.containsKey(h)) {
_fontMap.put(h, g2d.getFont().deriveFont(Font.BOLD, h));
}
font = _fontMap.get(h);
}
}
setFont(font);
setForeground(isBlack?Color.WHITE:Color.BLACK);
if (value1>0) {
if (!isBlack) g2d.drawString(""+value1, getWidth()*5/11, getHeight()*6/11);
else g2d.drawString(""+value1, getWidth()*3/4, getHeight()*1/3);
}
if (value2>0) {
g2d.drawString(""+value2, getWidth()*1/5, getHeight()*3/4);
}
}
}
/** Skeleton lekérése, minden esetben legyárt egy újat a {@link SkeletonPanel#_blacks} alapján! */
public ISkeletonTable getSkeleton() {
return new MySkeleton(_x, _y, _blacks, _difficulty);
}
/** Belső osztály, egy Skeletont ad meg. */
public static class MySkeleton implements ISkeletonTable {
/** Oszlopok száma */
int xx;
/** Sorok száma */
int yy;
/** Nehézségi szint */
int diff;
/** Feketéket tartalmazza csak! */
HashMap<Position, Boolean> map;
/** Alap konstruktor
*
* @param x oszlopok száma
* @param y sorok száma
* @param blacks feketéket tartalmazó map, a value értéke lényegtelen!
* @param difficulty nehézségi szint: {@link ITable#DIFFICULTY_EASY},{@link ITable#DIFFICULTY_MEDIUM},{@link ITable#DIFFICULTY_HARD}
* */
public MySkeleton(int x, int y, HashMap<Position, Boolean> blacks, int difficulty) {
xx = x;
yy = y;
map = new HashMap<Position, Boolean>(blacks);
diff = difficulty;
}
/** Oszlopok száma
*
* @return oszlopok
* */
public int getColumnCount() {
return xx;
}
/** Nehézségi szint lekérése.
*
* @return szint: {@link ITable#DIFFICULTY_EASY},{@link ITable#DIFFICULTY_MEDIUM},{@link ITable#DIFFICULTY_HARD}
* */
public int getDifficulty() {
return diff;
}
/** Sorok száma
*
* @return sorok
* */
public int getRowCount() {
return yy;
}
/** Adott pozíción található cella típusa
*
* @param position hely
* @return cella típusa: {@link ISkeletonTable#CELL_BLACK}, {@link ISkeletonTable#CELL_WHITE}
* */
public int getType(Position position) {
return (map.containsKey(position)?ISkeletonTable.CELL_BLACK:ISkeletonTable.CELL_WHITE);
}
public int getId() {
return -1;
}
}
/** Számértékeket betölt a kapott táblából, és ki is rajzolja őket.
*
* @param table kapott kitöltött tábla
* */
void showValues(ITable table) throws TableException {
_lastTable = table;
ISkeletonTable skeleton = getSkeleton();
if (table.getColumnCount()!=skeleton.getColumnCount() || table.getRowCount()!=skeleton.getRowCount())
throw new IndexOutOfBoundsException("Table size mismatch");
showValues = true;
for (int j=0; j<_y; j++) {
for (int i=0; i<_x; i++) {
Position p = new Position(i, j);
SkellyField field = _fields.get(p);
switch (table.getType(p)) {
case ITable.BLACK_NONE: field.setValues(0, 0); break;
case ITable.BLACK_BOTH: field.setValues(table.getSum(p, ITable.SUM_HORIZONTAL), table.getSum(p, ITable.SUM_VERTICAL)); break;
case ITable.BLACK_HORIZONTAL: field.setValues(table.getSum(p, ITable.SUM_HORIZONTAL), 0); break;
case ITable.BLACK_VERTICAL: field.setValues(0, table.getSum(p, ITable.SUM_VERTICAL)); break;
case ITable.WHITE: field.setValues(table.readCell(p, ITable.VALUE_TRUE), 0); break;
}
}
}
validate();
repaint();
}
/** Utolsóként kapott kitöltött tábla lekérése.
*
* @return utolsó tábla
* */
ITable getLastTable() {
return _lastTable;
}
}