/*
* $Id$
*
* Copyright (C) 2003-2015 JNode.org
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; If not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.jnode.apps.commander;
import charva.awt.BorderLayout;
import charva.awt.Color;
import charva.awt.EventQueue;
import charva.awt.Insets;
import charva.awt.Point;
import charva.awt.Toolkit;
import charva.awt.event.ActionEvent;
import charva.awt.event.ActionListener;
import charva.awt.event.KeyEvent;
import charva.awt.event.ScrollEvent;
import charvax.swing.JButton;
import charvax.swing.JFrame;
import charvax.swing.JLabel;
import charvax.swing.JList;
import charvax.swing.JOptionPane;
import charvax.swing.JPanel;
import charvax.swing.JScrollPane;
import charvax.swing.border.TitledBorder;
import charvax.swing.event.ListSelectionEvent;
import charvax.swing.event.ListSelectionListener;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
/**
* Charva based file manager.
*
* @author Levente S\u00e1ntha
*/
public class CharvaCommander extends JFrame {
private static final Color BACKGROUND_COLOR = Color.black;
private static final Color FOREGROUND_COLOR = Color.cyan;
private static final Color HIGHLIGHT_COLOR = Color.yellow;
private Pane leftPane;
private Pane rightPane;
private boolean exitted;
/**
* Startup method.
*
* @param argv ignored
*/
public static void main(String[] argv) {
CharvaCommander cmd = null;
try {
Toolkit.getDefaultToolkit().register();
cmd = new CharvaCommander();
cmd.setVisible(true);
//Toolkit.getDefaultToolkit().waitTillFinished();
} catch (Exception e) {
e.printStackTrace();
if (cmd != null)
cmd.exit();
else
Toolkit.getDefaultToolkit().close();
}
}
private CharvaCommander() {
this._insets = new Insets(0, 0, 0, 0);
setBackground(BACKGROUND_COLOR);
setForeground(FOREGROUND_COLOR);
JPanel content = (JPanel) getContentPane();
new ButtonPanel();
JPanel panel = new JPanel();
content.add(panel, BorderLayout.CENTER);
panel.setLayout(null);
File startf = new File(".");
String path = startf.getAbsolutePath();
try {
path = startf.getCanonicalPath();
} catch (IOException e) {
//ignore
}
leftPane = new Pane(38, 20);
leftPane.setPath(path);
leftPane.setLocation(0, 0);
panel.add(leftPane);
leftPane.label.setLocation(1, 22);
panel.add(leftPane.label);
rightPane = new Pane(38, 20);
rightPane.setPath(path);
rightPane.setLocation(40, 0);
panel.add(rightPane);
rightPane.label.setLocation(41, 22);
panel.add(rightPane.label);
setLocation(0, 0);
setSize(80, 24);
validate();
leftPane.list.requestFocus();
leftPane.border.setTitleColor(HIGHLIGHT_COLOR);
}
private class ButtonPanel extends JPanel implements ActionListener {
{
getContentPane().add(this, BorderLayout.SOUTH);
}
//final JButton help = createButton("1 Help", KeyEvent.VK_F1);
final JButton mkfile = createButton("F2 New", KeyEvent.VK_F2);
final JButton view = createButton("F3 View", KeyEvent.VK_F3);
final JButton edit = createButton("F4 Edit", KeyEvent.VK_F4);
final JButton copy = createButton("F5 Copy", KeyEvent.VK_F5);
final JButton move = createButton("F6 Move", KeyEvent.VK_F6);
final JButton mkdir = createButton("F7 MkDir", KeyEvent.VK_F7);
final JButton delte = createButton("F8 Delete", KeyEvent.VK_F8);
final JButton chdir = createButton("F9 ChDir", KeyEvent.VK_F9);
final JButton exit = createButton("F10 Exit", KeyEvent.VK_F10);
JButton createButton(String title, int accel) {
JButton b = new MyButton(title);
this.add(b);
b.setMnemonic(accel);
b.addActionListener(this);
return b;
}
public void actionPerformed(ActionEvent ae) {
try {
Object source = ae.getSource();
//if (source == help) {
//} else
if (source == mkfile) {
mkfile();
} else if (source == view) {
view();
} else if (source == edit) {
edit();
} else if (source == copy) {
copy();
} else if (source == move) {
move();
} else if (source == mkdir) {
mkdir();
} else if (source == delte) {
delete();
} else if (source == chdir) {
chdir();
} else if (source == exit) {
exit();
}
} finally {
if (!exitted)
leftPane.requestFocus();
}
}
}
private synchronized void move() {
String sel = leftPane.getSelection();
if (sel == null)
return;
int conf = JOptionPane
.showConfirmDialog(this, sel + " to " + rightPane.getPath(), "Move ?", JOptionPane.YES_NO_OPTION);
if (conf != JOptionPane.YES_OPTION)
return;
int index = leftPane.list.getSelectedIndex();
try {
File source = new File(leftPane.getPath(), sel);
if (copyRec(source, new File(rightPane.getPath(), sel), true))
delete0(source);
} finally {
leftPane.setPath(leftPane.getPath());
leftPane.list.setCurrentRow(index);
leftPane.repaint();
rightPane.setPath(rightPane.getPath(), sel);
rightPane.repaint();
}
}
private synchronized void view() {
String sel = leftPane.getSelection();
if (sel == null)
return;
File f = new File(leftPane.getPath(), sel);
if (!f.isFile())
return;
callViewer(f);
}
private synchronized void mkfile() {
String str = JOptionPane.showInputDialog(this, "File name: ", "New file", JOptionPane.OK_CANCEL_OPTION);
if (str == null || str.trim().length() == 0)
return;
File file = new File(leftPane.getPath(), str);
if (file.exists()) {
int conf = JOptionPane
.showConfirmDialog(this, file.getName(), "Continue? File already exits:", JOptionPane.YES_NO_OPTION);
if (conf != JOptionPane.YES_OPTION)
return;
} else {
try {
file.createNewFile();
} catch (IOException ioe) {
JOptionPane.showMessageDialog(this, "Could not create file: " + file.getName());
return;
}
}
callEditor(file);
leftPane.setPath(leftPane.getPath(), file.getName());
leftPane.repaint();
}
private void callViewer(File file) {
try {
Class.forName("org.jnode.apps.editor.TextEditor").getMethod("main", String[].class).
invoke(null, new Object[]{new String[]{file.getAbsolutePath(), "ro"}});
} catch (ClassNotFoundException e) {
JOptionPane.showMessageDialog(this, "Viewer not found", "Warning", JOptionPane.DEFAULT_OPTION);
} catch (Exception e) {
e.printStackTrace();
JOptionPane.showMessageDialog(this, e.getMessage(), "Error", JOptionPane.DEFAULT_OPTION);
}
}
private synchronized void edit() {
String sel = leftPane.getSelection();
if (sel == null)
return;
File f = new File(leftPane.getPath(), sel);
if (!f.isFile())
return;
callEditor(f);
}
private void callEditor(File file) {
try {
Class.forName("org.jnode.apps.editor.TextEditor").getMethod("main", String[].class).
invoke(null, new Object[]{new String[]{file.getAbsolutePath()}});
} catch (ClassNotFoundException e) {
JOptionPane.showMessageDialog(this, "Editor not found", "Warning", JOptionPane.DEFAULT_OPTION);
} catch (Exception e) {
e.printStackTrace();
JOptionPane.showMessageDialog(this, e.getMessage(), "Error", JOptionPane.DEFAULT_OPTION);
}
}
private synchronized void chdir() {
String str =
JOptionPane.showInputDialog(this, "Path to directory: ", "Change directory", JOptionPane.OK_CANCEL_OPTION);
if (str == null || str.trim().length() == 0)
return;
File f = str.charAt(0) == '/' ? new File(str) :
new File(leftPane.getPath(), str);
if (!f.exists())
JOptionPane.showMessageDialog(this, f.getAbsolutePath(), "Invalid directory", JOptionPane.DEFAULT_OPTION);
else {
if (!f.isDirectory())
f = f.getParentFile();
leftPane.setPath(f.getAbsolutePath());
leftPane.repaint();
}
}
private synchronized void delete() {
String sel = leftPane.getSelection();
if (sel == null)
return;
int conf = JOptionPane.showConfirmDialog(this, sel, "Delete ?", JOptionPane.YES_NO_OPTION);
if (conf != JOptionPane.YES_OPTION)
return;
File f = new File(leftPane.getPath(), sel);
if (f.isDirectory() && f.list().length > 0) {
conf = JOptionPane
.showConfirmDialog(this, "Directory is not empty: " + sel, "Delete ?", JOptionPane.YES_NO_OPTION);
if (conf != JOptionPane.YES_OPTION)
return;
}
int index = leftPane.list.getSelectedIndex();
try {
delete0(new File(leftPane.getPath(), sel));
} finally {
leftPane.setPath(leftPane.getPath());
leftPane.list.setCurrentRow(index);
leftPane.repaint();
}
}
private void delete0(File f) {
if (f.isDirectory())
for (String s : f.list())
delete0(new File(f.getAbsolutePath(), s));
f.delete();
}
@SuppressWarnings("unused")
private void notImplemented() {
JOptionPane.showMessageDialog(this, "NOT IMPLEMENTED!", null, JOptionPane.DEFAULT_OPTION);
leftPane.requestFocus();
}
private synchronized void copy() {
String sel = leftPane.getSelection();
if (sel == null)
return;
int conf = JOptionPane
.showConfirmDialog(this, sel + " to " + rightPane.getPath(), "Copy ?", JOptionPane.YES_NO_OPTION);
if (conf != JOptionPane.YES_OPTION)
return;
try {
copyRec(new File(leftPane.getPath(), sel), new File(rightPane.getPath(), sel), true);
} finally {
rightPane.setPath(rightPane.getPath(), sel);
rightPane.repaint();
}
}
private boolean copyRec(File from, File to, boolean ask) {
if (from.isFile()) {
return copy0(from, to, ask);
} else if (from.isDirectory()) {
if (ask && to.exists()) {
int conf = JOptionPane.showConfirmDialog(this, to.toString(), "Overwrite ?", JOptionPane.YES_NO_OPTION);
if (conf != JOptionPane.YES_OPTION)
return false;
} else
to.mkdir();
for (String name : from.list())
if (!copyRec(new File(from.getAbsolutePath(), name), new File(to.getAbsolutePath(), name), false))
return false;
return true;
} else {
JOptionPane.showMessageDialog(this, "Unsupported file type in source.");
return false;
}
}
private boolean copy0(File from, File to, boolean ask) {
if (from.equals(to)) {
JOptionPane.showMessageDialog(this, "Source and destination files are the same.");
return false;
}
if (ask && to.exists()) {
int conf = JOptionPane.showConfirmDialog(this, to.toString(), "Overwrite ?", JOptionPane.YES_NO_OPTION);
if (conf != JOptionPane.YES_OPTION)
return false;
}
try {
copy0(new FileInputStream(from), new FileOutputStream(to));
return true;
} catch (IOException e) {
JOptionPane.showMessageDialog(this, e.getMessage(), "Error", JOptionPane.OK_OPTION);
to.delete();
return false;
}
}
private void copy0(FileInputStream in, FileOutputStream out) throws IOException {
try {
byte[] buf = new byte[8 * 1024];
int c;
while ((c = in.read(buf)) > -1)
out.write(buf, 0, c);
out.flush();
} finally {
in.close();
out.close();
}
}
private synchronized void mkdir() {
String str = JOptionPane.showInputDialog(this, "Directory name: ", "Create directory",
JOptionPane.OK_CANCEL_OPTION);
if (str != null && str.trim().length() > 0) {
File file = new File(leftPane.getPath(), str);
file.mkdir();
leftPane.setPath(leftPane.getPath(), "/" + file.getName());
leftPane.repaint();
}
}
private synchronized void exit() {
exitted = true;
hide();
Toolkit.getDefaultToolkit().close();
}
private static void format(String str, int limit, StringBuilder sb) {
if (str.length() > limit) {
int half = limit / 2;
if (2 * half < limit) {
str = str.substring(0, half) + '~' + str.substring(str.length() - half);
} else {
str = str.substring(0, half) + '~' + str.substring(str.length() - half + 1);
}
}
sb.append(str);
int ln = str.length();
while (ln++ < limit) {
sb.append(' ');
}
}
private static class ListDataEntry implements Comparable {
final File file;
final String name;
private final String text;
ListDataEntry(File file, String text) {
this.file = file;
this.name = text;
this.text = text;
}
ListDataEntry(File file) {
SimpleDateFormat df = new SimpleDateFormat("dd/MM/yy HH:mm");
char vline = (char) Toolkit.ACS_VLINE;
this.file = file;
this.name = (file.isDirectory() ? "/" : "") + file.getName();
StringBuilder text = new StringBuilder();
text.append((file.isDirectory() ? '/' : ' '));
CharvaCommander.format(file.getName(), 15, text);
text.append(vline);
format(file.length(), text);
text.append(vline);
text.append(df.format(new Date(file.lastModified())));
this.text = text.toString();
}
private static final char[] UNITS = {'B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'};
private void format(long size, StringBuilder sb) {
String s = String.valueOf(size);
int index = 0;
while (s.length() > 5) {
size /= 1024;
s = String.valueOf(size);
index++;
}
int ln = s.length();
while (ln++ < 5) {
sb.append(' ');
}
sb.append(s);
sb.append(UNITS[index]);
}
public String toString() {
return text;
}
@Override
public int compareTo(Object o) {
ListDataEntry other = (ListDataEntry) o;
boolean thisDirectory = this.file.isDirectory();
if (!(thisDirectory ^ other.file.isDirectory())) {
return name.compareTo(other.name);
} else {
return thisDirectory ? -1 : 1;
}
}
}
private class Pane extends JScrollPane implements ListSelectionListener {
String path;
MyList list;
TitledBorder border;
JLabel label = new JLabel();
int w;
int h;
Pane(int w, int h) {
this.w = w;
this.h = h;
border = new TitledBorder("");
border.setTitleColor(FOREGROUND_COLOR);
list = new MyList();
setViewportView(list);
list.setVisibleRowCount(h);
list.setColumns(w);
list.addListSelectionListener(this);
this.setViewportBorder(border);
this.setForeground(FOREGROUND_COLOR);
}
private void handleFocus() {
if (leftPane != this) {
rightPane = leftPane;
leftPane = this;
leftPane.border.setTitleColor(HIGHLIGHT_COLOR);
rightPane.border.setTitleColor(FOREGROUND_COLOR);
rightPane.repaint();
}
}
private int setData(String s, String n) {
File file = new File(s);
if (file.isDirectory()) {
setPath0(s);
File[] files = file.listFiles();
ListDataEntry[] names = new ListDataEntry[files == null ? 0 : files.length];
//prefix dirs with '/'
int i = 0;
if (files != null) {
for (File f : files)
names[i++] = new ListDataEntry(f);
}
Arrays.sort(names);
//add parent entry: ".."
if (!"/".equals(s)) {
int ln = names.length;
ListDataEntry[] names2 = new ListDataEntry[ln + 1];
names2[0] = new ListDataEntry(file, "..");
System.arraycopy(names, 0, names2, 1, ln);
names = names2;
}
list.setListData(names);
//set list selection
if (n == null) {
list.setSelectedIndex(0);
return 0;
} else {
i = 0;
for (ListDataEntry nn : names) {
if (n.equals(nn.name)) {
return i;
}
i++;
}
return 0;
}
}
return -1;
}
public String getPath() {
return path;
}
public void setPath(String path) {
setPath0(path);
setData(path, null);
}
public void setPath(String path, String selection) {
setPath0(path);
int index = setData(path, selection);
list.setCurrentRow(index);
}
public String getSelection() {
String sel = ((ListDataEntry) list.getSelectedValue()).name;
if (sel == null || sel.equals(".."))
return null;
if (sel.charAt(0) == '/')
return sel.substring(1);
return sel;
}
private void setPath0(String path) {
this.path = path;
setBorder(path);
}
private void setBorder(String path) {
int w = this.w - 2;
if (w > 0 && path.length() > w) {
path = path.substring(path.length() - w);
}
border.setTitle(path);
}
@Override
public void valueChanged(ListSelectionEvent evt) {
if (label != null) {
if (list.getModel().getSize() > evt.getFirstIndex()) {
String selection = getSelection();
if (selection == null) {
label.setText("");
} else {
StringBuilder sb = new StringBuilder();
format(selection, 38, sb);
label.setText(sb.toString());
}
}
}
}
private class MyList extends JList {
@Override
public void processKeyEvent(KeyEvent ke) {
EventQueue evtqueue = Toolkit.getDefaultToolkit().getSystemEventQueue();
int keyCode = ke.getKeyCode();
if (keyCode == KeyEvent.VK_ENTER) {
Object sel = ((ListDataEntry) list.getSelectedValue()).name;
String s;
String n = null;
if ("..".equals(sel)) {
File file = new File(path);
n = file.getName();
s = file.getParentFile().getAbsolutePath();
} else {
String sels = String.valueOf(sel);
s = new File(path, sels.substring(1, sels.length())).getAbsolutePath();
}
int sri = setData(s, "/" + n);
int dir = _currentRow < sri ? ScrollEvent.UP : ScrollEvent.DOWN;
if (sri > -1)
_currentRow = sri;
setSelectedIndex(_currentRow);
evtqueue.postEvent(new ScrollEvent(this, dir, new Point(0, _currentRow)));
ke.consume();
} else if (keyCode == KeyEvent.VK_HOME) {
_currentRow = 0;
evtqueue.postEvent(new ScrollEvent(
this, ScrollEvent.DOWN, new Point(0, _currentRow)));
}
super.processKeyEvent(ke);
if (keyCode == KeyEvent.VK_UP ||
keyCode == KeyEvent.VK_DOWN ||
keyCode == KeyEvent.VK_PAGE_UP ||
keyCode == KeyEvent.VK_PAGE_DOWN ||
keyCode == KeyEvent.VK_END ||
keyCode == KeyEvent.VK_HOME) {
setSelectedIndex(_currentRow);
}
}
@Override
public void requestFocus() {
super.requestFocus();
handleFocus();
}
void setCurrentRow(int index) {
if (index > -1) {
int size = list.getModel().getSize();
if (index >= size) {
index = size - 1;
}
int dir = _currentRow < index ? ScrollEvent.UP : ScrollEvent.DOWN;
_currentRow = index;
setSelectedIndex(_currentRow);
Toolkit.getDefaultToolkit().getSystemEventQueue().
postEvent(new ScrollEvent(this, dir, new Point(0, _currentRow)));
}
}
}
}
private static class MyButton extends JButton {
public MyButton() {
this("");
}
public MyButton(String text_) {
super(text_);
}
@Override
public boolean isFocusTraversable() {
return false;
}
@Override
protected String getLabelString() {
return getText();
}
public int getWidth() {
return getLabelString().length();
}
@Override
public void draw(Toolkit toolkit) {
Point origin = getLocationOnScreen();
toolkit.setCursor(origin);
int colorpair = getCursesColor();
toolkit.addString(getLabelString(), Toolkit.A_REVERSE, colorpair);
}
}
}