2015-06-01 09:35:51 +00:00
|
|
|
package com.bytezone.diskbrowser.gui;
|
|
|
|
|
2016-02-28 05:41:10 +00:00
|
|
|
import java.awt.*;
|
2015-06-01 09:35:51 +00:00
|
|
|
import java.awt.event.InputEvent;
|
2016-03-01 07:38:20 +00:00
|
|
|
import java.awt.event.KeyAdapter;
|
|
|
|
import java.awt.event.KeyEvent;
|
2015-06-01 09:35:51 +00:00
|
|
|
import java.awt.event.MouseAdapter;
|
|
|
|
import java.awt.event.MouseEvent;
|
2016-07-30 03:04:55 +00:00
|
|
|
import java.awt.geom.AffineTransform;
|
2015-06-01 09:35:51 +00:00
|
|
|
import java.util.List;
|
|
|
|
|
2015-06-02 01:35:49 +00:00
|
|
|
import javax.swing.JPanel;
|
2015-06-01 09:35:51 +00:00
|
|
|
import javax.swing.Scrollable;
|
|
|
|
import javax.swing.SwingConstants;
|
|
|
|
|
|
|
|
import com.bytezone.diskbrowser.disk.Disk;
|
|
|
|
import com.bytezone.diskbrowser.disk.DiskAddress;
|
|
|
|
import com.bytezone.diskbrowser.disk.FormattedDisk;
|
|
|
|
import com.bytezone.diskbrowser.disk.SectorType;
|
|
|
|
import com.bytezone.diskbrowser.gui.DiskLayoutPanel.LayoutDetails;
|
|
|
|
import com.bytezone.diskbrowser.gui.RedoHandler.RedoEvent;
|
2016-03-03 07:04:47 +00:00
|
|
|
import com.bytezone.diskbrowser.gui.RedoHandler.RedoListener;
|
2015-06-01 09:35:51 +00:00
|
|
|
|
2016-03-03 07:04:47 +00:00
|
|
|
class DiskLayoutImage extends JPanel implements Scrollable, RedoListener
|
2015-06-01 09:35:51 +00:00
|
|
|
{
|
2016-07-18 08:55:32 +00:00
|
|
|
private static final Cursor crosshairCursor = new Cursor (Cursor.CROSSHAIR_CURSOR);
|
|
|
|
private static final Color[] lightColors =
|
|
|
|
{ Color.WHITE, Color.YELLOW, Color.PINK, Color.CYAN, Color.ORANGE, Color.GREEN };
|
2016-02-28 05:41:10 +00:00
|
|
|
|
|
|
|
private FormattedDisk disk;
|
2016-03-01 07:38:20 +00:00
|
|
|
private LayoutDetails layoutDetails;
|
2015-06-01 09:35:51 +00:00
|
|
|
private boolean showFreeSectors;
|
2016-03-01 07:38:20 +00:00
|
|
|
private final DiskLayoutSelection selectionHandler = new DiskLayoutSelection ();
|
|
|
|
private boolean redo;
|
2017-01-20 05:20:00 +00:00
|
|
|
private boolean isRetina;
|
2015-06-01 09:35:51 +00:00
|
|
|
|
|
|
|
// set defaults (used until a real disk is set)
|
2016-03-01 07:38:20 +00:00
|
|
|
private int bw = 30;
|
|
|
|
private int bh = 15;
|
|
|
|
private int gw = 8;
|
|
|
|
private int gh = 35;
|
2015-06-01 09:35:51 +00:00
|
|
|
|
|
|
|
public DiskLayoutImage ()
|
|
|
|
{
|
|
|
|
setPreferredSize (new Dimension (240 + 1, 525 + 1));
|
|
|
|
addMouseListener (new MyMouseListener ());
|
|
|
|
setBackground (Color.WHITE);
|
|
|
|
setOpaque (true);
|
2016-03-01 07:38:20 +00:00
|
|
|
|
|
|
|
addKeyListener (new MyKeyListener ());
|
2015-06-01 09:35:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public void setDisk (FormattedDisk disk, LayoutDetails details)
|
|
|
|
{
|
|
|
|
this.disk = disk;
|
|
|
|
layoutDetails = details;
|
|
|
|
|
|
|
|
bw = layoutDetails.block.width;
|
|
|
|
bh = layoutDetails.block.height;
|
|
|
|
gw = layoutDetails.grid.width;
|
|
|
|
gh = layoutDetails.grid.height;
|
|
|
|
|
|
|
|
setPreferredSize (new Dimension (gw * bw + 1, gh * bh + 1));
|
|
|
|
selectionHandler.setSelection (null);
|
|
|
|
|
2016-07-30 03:04:55 +00:00
|
|
|
Graphics2D g = (Graphics2D) this.getGraphics ();
|
2017-01-20 05:20:00 +00:00
|
|
|
if (g != null) // panel might not be showing
|
|
|
|
isRetina = g.getFontRenderContext ().getTransform ()
|
|
|
|
.equals (AffineTransform.getScaleInstance (2.0, 2.0));
|
2016-07-30 03:04:55 +00:00
|
|
|
|
2015-06-01 09:35:51 +00:00
|
|
|
repaint ();
|
|
|
|
}
|
|
|
|
|
2016-02-28 05:41:10 +00:00
|
|
|
public FormattedDisk getDisk ()
|
|
|
|
{
|
|
|
|
return disk;
|
|
|
|
}
|
|
|
|
|
2015-06-01 09:35:51 +00:00
|
|
|
public void setShowFreeSectors (boolean showFree)
|
|
|
|
{
|
|
|
|
showFreeSectors = showFree;
|
|
|
|
repaint ();
|
|
|
|
}
|
|
|
|
|
|
|
|
void setSelection (List<DiskAddress> sectors)
|
|
|
|
{
|
|
|
|
selectionHandler.setSelection (sectors);
|
|
|
|
if (sectors != null && sectors.size () > 0)
|
|
|
|
{
|
|
|
|
DiskAddress da = sectors.size () == 1 ? sectors.get (0) : sectors.get (1);
|
|
|
|
scrollRectToVisible (layoutDetails.getLocation (da));
|
|
|
|
}
|
|
|
|
repaint ();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected void paintComponent (Graphics g)
|
|
|
|
{
|
|
|
|
super.paintComponent (g);
|
|
|
|
|
|
|
|
if (disk == null)
|
|
|
|
return;
|
|
|
|
|
|
|
|
Rectangle clipRect = g.getClipBounds ();
|
|
|
|
|
|
|
|
Point p1 = new Point (clipRect.x / bw * bw, clipRect.y / bh * bh);
|
2016-02-28 05:41:10 +00:00
|
|
|
Point p2 = new Point ((clipRect.x + clipRect.width - 1) / bw * bw,
|
|
|
|
(clipRect.y + clipRect.height - 1) / bh * bh);
|
2015-06-01 09:35:51 +00:00
|
|
|
|
|
|
|
int maxBlock = gw * gh;
|
|
|
|
Disk d = disk.getDisk ();
|
|
|
|
|
|
|
|
// this stops an index error when using alt-5 to switch to 512-byte blocks
|
2016-02-28 18:53:59 +00:00
|
|
|
// if (maxBlock > d.getTotalBlocks ())
|
|
|
|
// maxBlock = d.getTotalBlocks ();
|
|
|
|
// the index error is caused by not recalculating the grid layout
|
2015-06-01 09:35:51 +00:00
|
|
|
|
2016-07-29 12:28:11 +00:00
|
|
|
Graphics2D g2d = (Graphics2D) g;
|
|
|
|
|
2015-06-01 09:35:51 +00:00
|
|
|
for (int y = p1.y; y <= p2.y; y += bh)
|
|
|
|
for (int x = p1.x; x <= p2.x; x += bw)
|
|
|
|
{
|
|
|
|
int blockNo = y / bh * gw + x / bw;
|
|
|
|
if (blockNo < maxBlock)
|
|
|
|
{
|
|
|
|
DiskAddress da = d.getDiskAddress (blockNo);
|
2016-07-29 12:28:11 +00:00
|
|
|
boolean free = showFreeSectors && disk.isSectorFree (da);
|
|
|
|
boolean selected = selectionHandler.isSelected (da);
|
|
|
|
drawBlock (g2d, blockNo, x, y, free, selected);
|
2015-06-01 09:35:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void drawBlock (Graphics2D g, int blockNo, int x, int y, boolean flagFree,
|
2015-06-02 01:35:49 +00:00
|
|
|
boolean selected)
|
2015-06-01 09:35:51 +00:00
|
|
|
{
|
|
|
|
SectorType type = disk.getSectorType (blockNo);
|
|
|
|
int offset = (bw - 4) / 2 + 1;
|
|
|
|
|
|
|
|
Rectangle rect = new Rectangle (x, y, bw, bh);
|
|
|
|
|
2017-01-20 05:20:00 +00:00
|
|
|
int width = rect.width - (isRetina ? 2 : 3);
|
|
|
|
int height = rect.height - (isRetina ? 2 : 3);
|
|
|
|
int offset2 = isRetina ? 1 : 2;
|
2016-07-30 06:12:01 +00:00
|
|
|
|
2015-06-01 09:35:51 +00:00
|
|
|
// draw frame
|
2016-07-30 06:12:01 +00:00
|
|
|
if (true)
|
2015-06-01 09:35:51 +00:00
|
|
|
{
|
|
|
|
g.setColor (Color.GRAY);
|
2016-07-30 06:12:01 +00:00
|
|
|
// g.drawRect (rect.x, rect.y, rect.width, rect.height);
|
|
|
|
g.draw (rect);
|
2015-06-01 09:35:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// draw coloured block
|
|
|
|
if (type.colour != Color.WHITE)
|
|
|
|
{
|
|
|
|
g.setColor (type.colour);
|
2016-07-30 06:12:01 +00:00
|
|
|
g.fillRect (rect.x + offset2, rect.y + offset2, width, height);
|
2015-06-01 09:35:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// draw an indicator in free blocks
|
|
|
|
if (flagFree)
|
|
|
|
{
|
|
|
|
g.setColor (getContrastColor (type));
|
|
|
|
g.drawOval (rect.x + offset - 2, rect.y + 4, 7, 7);
|
|
|
|
}
|
|
|
|
|
|
|
|
// draw an indicator in selected blocks
|
|
|
|
if (selected)
|
|
|
|
{
|
|
|
|
g.setColor (getContrastColor (type));
|
|
|
|
g.fillOval (rect.x + offset, rect.y + 6, 3, 3);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private Color getContrastColor (SectorType type)
|
|
|
|
{
|
2016-07-18 08:55:32 +00:00
|
|
|
for (Color color : lightColors)
|
|
|
|
if (type.colour == color)
|
|
|
|
return Color.BLACK;
|
2015-06-01 09:35:51 +00:00
|
|
|
return Color.WHITE;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Dimension getPreferredScrollableViewportSize ()
|
|
|
|
{
|
|
|
|
return new Dimension (240 + 1, 525 + 1); // floppy disk size
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2015-06-02 01:35:49 +00:00
|
|
|
public int getScrollableUnitIncrement (Rectangle visibleRect, int orientation,
|
|
|
|
int direction)
|
2015-06-01 09:35:51 +00:00
|
|
|
{
|
|
|
|
return orientation == SwingConstants.HORIZONTAL ? bw : bh;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2015-06-02 01:35:49 +00:00
|
|
|
public int getScrollableBlockIncrement (Rectangle visibleRect, int orientation,
|
|
|
|
int direction)
|
2015-06-01 09:35:51 +00:00
|
|
|
{
|
|
|
|
return orientation == SwingConstants.HORIZONTAL ? bw * 4 : bh * 10;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean getScrollableTracksViewportHeight ()
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean getScrollableTracksViewportWidth ()
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-03-03 07:04:47 +00:00
|
|
|
@Override
|
|
|
|
public void redo (RedoEvent redoEvent)
|
2015-06-01 09:35:51 +00:00
|
|
|
{
|
|
|
|
redo = true;
|
|
|
|
SectorSelectedEvent event = (SectorSelectedEvent) redoEvent.value;
|
|
|
|
setSelection (event.getSectors ());
|
|
|
|
fireSectorSelectionEvent (event);
|
|
|
|
redo = false;
|
2016-03-01 08:51:15 +00:00
|
|
|
|
|
|
|
requestFocusInWindow ();
|
2015-06-01 09:35:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private void fireSectorSelectionEvent ()
|
|
|
|
{
|
|
|
|
SectorSelectedEvent event =
|
2015-06-02 01:35:49 +00:00
|
|
|
new SectorSelectedEvent (this, selectionHandler.getHighlights (), disk);
|
2015-06-01 09:35:51 +00:00
|
|
|
fireSectorSelectionEvent (event);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void fireSectorSelectionEvent (SectorSelectedEvent event)
|
|
|
|
{
|
|
|
|
event.redo = redo;
|
|
|
|
SectorSelectionListener[] listeners =
|
2015-06-02 01:35:49 +00:00
|
|
|
(listenerList.getListeners (SectorSelectionListener.class));
|
2015-06-01 09:35:51 +00:00
|
|
|
for (SectorSelectionListener listener : listeners)
|
|
|
|
listener.sectorSelected (event);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void addSectorSelectionListener (SectorSelectionListener listener)
|
|
|
|
{
|
|
|
|
listenerList.add (SectorSelectionListener.class, listener);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void removeSectorSelectionListener (SectorSelectionListener listener)
|
|
|
|
{
|
|
|
|
listenerList.remove (SectorSelectionListener.class, listener);
|
|
|
|
}
|
|
|
|
|
2016-03-01 07:38:20 +00:00
|
|
|
class MyKeyListener extends KeyAdapter
|
|
|
|
{
|
|
|
|
@Override
|
|
|
|
public void keyPressed (KeyEvent e)
|
|
|
|
{
|
|
|
|
switch (e.getKeyCode ())
|
|
|
|
{
|
|
|
|
case KeyEvent.VK_LEFT:
|
|
|
|
case KeyEvent.VK_RIGHT:
|
|
|
|
case KeyEvent.VK_UP:
|
|
|
|
case KeyEvent.VK_DOWN:
|
2016-07-21 09:08:39 +00:00
|
|
|
selectionHandler.cursorMove (disk, e);
|
2016-03-01 07:38:20 +00:00
|
|
|
fireSectorSelectionEvent ();
|
|
|
|
repaint ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-01 09:35:51 +00:00
|
|
|
class MyMouseListener extends MouseAdapter
|
|
|
|
{
|
|
|
|
private Cursor currentCursor;
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void mouseClicked (MouseEvent e)
|
|
|
|
{
|
|
|
|
int x = e.getX () / bw;
|
|
|
|
int y = e.getY () / bh;
|
|
|
|
int blockNo = y * gw + x;
|
|
|
|
DiskAddress da = disk.getDisk ().getDiskAddress (blockNo);
|
|
|
|
|
|
|
|
boolean extend = ((e.getModifiersEx () & InputEvent.SHIFT_DOWN_MASK) > 0);
|
|
|
|
boolean append = ((e.getModifiersEx () & InputEvent.CTRL_DOWN_MASK) > 0);
|
|
|
|
|
|
|
|
selectionHandler.doClick (disk.getDisk (), da, extend, append);
|
|
|
|
fireSectorSelectionEvent ();
|
|
|
|
repaint ();
|
2016-03-01 07:38:20 +00:00
|
|
|
requestFocusInWindow ();
|
2015-06-01 09:35:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void mouseEntered (MouseEvent e)
|
|
|
|
{
|
|
|
|
currentCursor = getCursor ();
|
|
|
|
setCursor (crosshairCursor);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void mouseExited (MouseEvent e)
|
|
|
|
{
|
|
|
|
setCursor (currentCursor);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|