DUMMY, // <-- TODO : remove
KEY_ACTION_BACK, KEY_ACTION_HELP, // MainWindow
KEY_ACTION_VIEW_CARD, // FileList
- KEY_ACTION_VIEW_CONTACT, KEY_ACTION_EDIT_CONTACT, KEY_ACTION_SAVE_CARD, KEY_ACTION_DELETE_CONTACT, KEY_ACTION_SWITCH_FORMAT, // ContactList
+ KEY_ACTION_VIEW_CONTACT, KEY_ACTION_EDIT_CONTACT, KEY_ACTION_SAVE_CARD, KEY_ACTION_DELETE_CONTACT, KEY_ACTION_SEARCH, // ContactList
DEAULT_FIELD_SEPARATOR, DEAULT_FIELD_SEPARATOR_NOUTF, // MainContentList
+ KEY_ACTION_INVERT, KEY_ACTION_FULLSCREEN, // ContactDetails
+ KEY_ACTION_SWITCH_FORMAT, // multi-usage
NULL; // Special usage
public String trans() {
map.put(StringId.KEY_ACTION_EDIT_CONTACT, "Edit");
map.put(StringId.KEY_ACTION_DELETE_CONTACT, "Delete");
map.put(StringId.KEY_ACTION_SWITCH_FORMAT, "Change view");
+ map.put(StringId.KEY_ACTION_INVERT, "Invert colours");
+ map.put(StringId.KEY_ACTION_FULLSCREEN, "Fullscreen");
+ map.put(StringId.KEY_ACTION_SEARCH, "Search");
}
}
private Mode mode;
private boolean invert;
+ /**
+ * Th rendering modes supported by this {@link ImageText} to convert
+ * {@link Image}s into text.
+ *
+ * @author niki
+ *
+ */
public enum Mode {
/**
* Use 5 different "colours" which are actually Unicode
ASCII,
}
+ /**
+ * Create a new {@link ImageText} with the given parameters. Defaults to
+ * {@link Mode#DOUBLE_DITHERING} and no colour inversion.
+ *
+ * @param image
+ * the source {@link Image}
+ * @param size
+ * the final text size to target
+ */
+ public ImageText(Image image, TerminalSize size) {
+ this(image, size, Mode.DOUBLE_DITHERING, false);
+ }
+
/**
* Create a new {@link ImageText} with the given parameters.
*
* the final text size to target
* @param mode
* the mode of conversion
+ * @param invert
+ * TRUE to invert colours rendering
*/
- public ImageText(Image image, TerminalSize size, Mode mode) {
- setImage(image, size);
+ public ImageText(Image image, TerminalSize size, Mode mode, boolean invert) {
+ setImage(image);
+ setSize(size);
setMode(mode);
+ setColorInvert(invert);
}
/**
* the new {@link Image}
*/
public void setImage(Image image) {
- setImage(image, size);
- }
-
- /**
- * Change the source {@link Image}.
- *
- * @param size
- * the size to use
- */
- public void setImage(TerminalSize size) {
- setImage(image, size);
+ this.text = null;
+ this.ready = false;
+ this.image = image;
}
/**
- * Change the source {@link Image}.
+ * Change the target size of this {@link ImageText}.
*
- * @param image
- * the new {@link Image}
* @param size
- * the size to use
+ * the new size
*/
- public void setImage(Image image, TerminalSize size) {
+ public void setSize(TerminalSize size) {
this.text = null;
this.ready = false;
this.size = size;
- if (image != null) {
- this.image = image;
- }
}
/**
*/
public String getText() {
if (text == null) {
- if (image == null)
+ if (image == null || size == null || size.getColumns() == 0
+ || size.getRows() == 0)
return "";
int mult = 1;
int x = 0;
int y = 0;
- if (srcSize.getColumns() > srcSize.getRows()) {
+ if (srcSize.getColumns() < srcSize.getRows()) {
double ratio = (double) size.getColumns()
/ (double) size.getRows();
ratio *= (double) srcSize.getRows()
import com.googlecode.lanterna.gui2.Panel;
import com.googlecode.lanterna.gui2.TextBox;
+/**
+ * A {@link Panel} containing an {@link ImageText} rendering.
+ *
+ * @author niki
+ *
+ */
public class ImageTextControl extends Panel {
- private ImageText img;
+ private ImageText image;
private TextBox txt;
private int mode;
+ /**
+ * Create a new {@link ImageTextControl} for the given {@link Image} and
+ * {@link TerminalSize}.
+ *
+ * @param image
+ * the {@link Image} to render
+ * @param size
+ * the target size of this control
+ */
public ImageTextControl(Image image, TerminalSize size) {
Mode mode = Mode.DOUBLE_DITHERING;
if (!UiColors.getInstance().isUnicode()) {
}
this.setLayoutManager(new BorderLayout());
- setImg(new ImageText(image, size, mode));
+ setSize(size);
+ setImage(new ImageText(image, size, mode, false));
}
+ /**
+ * Cycle through the available rendering modes if possible.
+ *
+ * @return TRUE if it was possible to switch modes
+ */
public boolean switchMode() {
- if (img == null || !UiColors.getInstance().isUnicode())
+ if (image == null || !UiColors.getInstance().isUnicode())
return false;
Mode[] modes = Mode.values();
if (mode >= modes.length)
mode = 0;
- img.setMode(modes[mode]);
- setImg(img);
+ image.setMode(modes[mode]);
+ setImage(image);
return true;
}
+ /**
+ * Invert the colours.
+ */
public void invertColor() {
- if (img != null) {
- img.setColorInvert(!img.isColorInvert());
- setImg(img);
+ if (image != null) {
+ image.setColorInvert(!image.isColorInvert());
+ setImage(image);
}
}
- private void setImg(ImageText img) {
- this.img = img;
+ @Override
+ public synchronized Panel setSize(TerminalSize size) {
+ if (image != null)
+ image.setSize(size);
+
+ super.setSize(size);
+
+ setImage(image);
+
+ return this;
+ };
+
+ /**
+ * Set/reset the {@link ImageText} to render.
+ *
+ * @param image
+ * the new {@link ImageText}
+ */
+ private void setImage(ImageText image) {
+ this.image = image;
removeAllComponents();
txt = null;
- if (img != null) {
- txt = new TextBox(img.getText());
+ if (image != null) {
+ txt = new TextBox(image.getText());
this.addComponent(txt, BorderLayout.Location.CENTER);
}
}
*
*/
public enum Mode {
- NONE, MOVE, BACK, HELP, FILE_LIST, CONTACT_LIST, CONTACT_DETAILS_RAW, CONTACT_DETAILS, EDIT_DETAIL, DELETE_CONTACT, SAVE_CARD,
+ NONE, MOVE, BACK, HELP, FILE_LIST, CONTACT_LIST, CONTACT_DETAILS_RAW, CONTACT_DETAILS, ASK_USER, ASK_USER_KEY,
}
public enum DataType {
public boolean onAction() {
return true;
}
+
+ /**
+ * Used to callback a function from the menu when the user has to introduce
+ * some text.
+ *
+ * @param answer
+ * the user answer
+ *
+ * @return an error message if any
+ */
+ public String callback(String answer) {
+ return null;
+ }
+
+ /**
+ * When asking a question to the user, return the question.
+ *
+ * @return the question
+ */
+ public String getQuestion() {
+ return null;
+ }
+
+ /**
+ * When asking a question to the user (not for one-key mode), return the
+ * default answer.
+ *
+ * @return the default answer
+ */
+ public String getDefaultAnswer() {
+ return null;
+ }
}
private String handleQuestion(KeyStroke key) {
String answer = null;
+ // TODO: support ^H (backspace)
+ // TODO: start at end of initial question, not start
+
if (waitForOneKeyAnswer) {
answer = "" + key.getCharacter();
} else {
private void handleAction(KeyAction action, String answer) {
MainContent content = getContent();
- Card card = action.getCard();
- Contact contact = action.getContact();
- Data data = action.getData();
-
switch (action.getMode()) {
case MOVE:
int x = 0;
break;
// mode with windows:
case CONTACT_LIST:
- if (card != null) {
- pushContent(new ContactList(card));
+ if (action.getCard() != null) {
+ pushContent(new ContactList(action.getCard()));
}
break;
case CONTACT_DETAILS:
- if (contact != null) {
- pushContent(new ContactDetails(contact));
+ if (action.getContact() != null) {
+ pushContent(new ContactDetails(action.getContact()));
}
break;
case CONTACT_DETAILS_RAW:
- if (contact != null) {
- pushContent(new ContactDetailsRaw(contact));
+ if (action.getContact() != null) {
+ pushContent(new ContactDetailsRaw(action.getContact()));
}
break;
// mode interpreted by MainWindow:
break;
// action modes:
- case EDIT_DETAIL:
+ case ASK_USER:
if (answer == null) {
- if (data != null) {
- String name = data.getName();
- String value = data.getValue();
- setQuestion(action, name, value);
- }
+ setQuestion(action, action.getQuestion(),
+ action.getDefaultAnswer());
} else {
- setMessage(null, false);
- data.setValue(answer);
+ setMessage(action.callback(answer), true);
+ content.refreshData();
+ invalidate();
+ setTitle();
}
break;
- case DELETE_CONTACT:
+ case ASK_USER_KEY:
if (answer == null) {
- if (contact != null) {
- setQuestion(action, "Delete contact? [Y/N]");
- }
+ setQuestion(action, action.getQuestion());
} else {
- setMessage(null, false);
- if (answer.equalsIgnoreCase("y")) {
- if (contact.delete()) {
- content.refreshData();
- invalidate();
- setTitle();
- } else {
- setMessage("Cannot delete this contact", true);
- }
- }
- }
- break;
- case SAVE_CARD:
- if (answer == null) {
- if (card != null) {
- setQuestion(action, "Save changes? [Y/N]");
- }
- } else {
- setMessage(null, false);
- if (answer.equalsIgnoreCase("y")) {
- boolean ok = false;
- try {
- if (card.save()) {
- ok = true;
- invalidate();
- }
- } catch (IOException ioe) {
- ioe.printStackTrace();
- }
-
- if (!ok) {
- setMessage("Cannot save to file", true);
- }
- }
+ setMessage(action.callback(answer), true);
+ content.refreshData();
+ invalidate();
+ setTitle();
}
break;
default:
public class ContactDetails extends MainContent {
private Contact contact;
+ private Panel top;
private ImageTextControl txt;
+ private Image image;
+ private boolean fullscreenImage;
public ContactDetails(Contact contact) {
this.contact = contact;
BorderLayout blayout = new BorderLayout();
setLayoutManager(blayout);
- Panel top = new Panel();
+ top = new Panel();
+ setContact(contact);
+ addComponent(top, BorderLayout.Location.TOP);
+ }
+
+ public void setContact(Contact contact) {
+ Image img = null;
+ this.contact = contact;
+
if (contact != null) {
Data photo = contact.getPreferredData("PHOTO");
if (photo != null) {
if (encoding != null && encoding.getValue() != null
&& encoding.getValue().equalsIgnoreCase("b")) {
- Image img = new ImageIcon(Base64.getDecoder().decode(
+ img = new ImageIcon(Base64.getDecoder().decode(
photo.getValue())).getImage();
-
- TerminalSize size = new TerminalSize(40, 20);
- size = new TerminalSize(100, 50);
-
- txt = new ImageTextControl(img, size);
- top.addComponent(txt);
}
}
}
- addComponent(top, BorderLayout.Location.TOP);
+ setImage(img);
}
@Override
}
});
actions.add(new KeyAction(Mode.NONE, 'i',
- Trans.StringId.DUMMY) {
+ Trans.StringId.KEY_ACTION_INVERT) {
@Override
public boolean onAction() {
if (txt != null) {
return false;
}
});
+ actions.add(new KeyAction(Mode.NONE, 'f',
+ Trans.StringId.KEY_ACTION_FULLSCREEN) {
+ @Override
+ public boolean onAction() {
+ fullscreenImage = !fullscreenImage;
+ setImage(image);
+ return false;
+ }
+ });
return actions;
}
+
+ @Override
+ public synchronized Panel setSize(TerminalSize size) {
+ super.setSize(size);
+ setImage(image);
+ return this;
+ }
+
+ /**
+ * Set the {@link Image} to render.
+ *
+ * @param image
+ * the new {@link Image}
+ */
+ private void setImage(Image image) {
+ this.image = image;
+
+ TerminalSize size = getTxtSize();
+ if (size != null) {
+ if (txt != null)
+ txt.setSize(size);
+ else
+ txt = new ImageTextControl(image, size);
+ }
+
+ if (top.getChildCount() > 0)
+ top.removeAllComponents();
+
+ if (size != null)
+ top.addComponent(txt);
+ }
+
+ /**
+ * Compute the size to use for the {@link Image} text rendering. Return NULL
+ * in case of error.
+ *
+ * @return the {@link TerminalSize} to use or NULL if it is not possible
+ */
+ private TerminalSize getTxtSize() {
+ if (image != null && getSize() != null && getSize().getColumns() > 0
+ && getSize().getRows() > 0) {
+ if (fullscreenImage) {
+ return getSize();
+ } else {
+ // TODO:
+ return new TerminalSize(40, 20);
+ }
+ }
+
+ return null;
+ }
}
public DataType getDataType() {
return DataType.DATA;
}
-
+
@Override
public List<KeyAction> getKeyBindings() {
// TODO Auto-generated method stub
List<KeyAction> actions = new LinkedList<KeyAction>();
// TODO: add, remove
- actions.add(new KeyAction(Mode.EDIT_DETAIL, 'd', Trans.StringId.DUMMY) {
+ actions.add(new KeyAction(Mode.ASK_USER , KeyType.Enter,
+ Trans.StringId.DUMMY) {
@Override
public Object getObject() {
return contact.getContent().get(getSelectedIndex());
}
+
+ @Override
+ public String getQuestion() {
+ Data data = getData();
+ if (data != null) {
+ return data.getName();
+ }
+
+ return null;
+ }
+
+ @Override
+ public String getDefaultAnswer() {
+ Data data = getData();
+ if (data != null) {
+ return data.getValue();
+ }
+
+ return null;
+ }
+
+ @Override
+ public String callback(String answer) {
+ Data data = getData();
+ if (data != null) {
+ data.setValue(answer);
+ return null;
+ }
+
+ // TODO: i18n
+ return "Cannot modify value";
+ }
});
actions.add(new KeyAction(Mode.NONE, KeyType.Tab,
Trans.StringId.KEY_ACTION_SWITCH_FORMAT) {
package be.nikiroo.jvcard.tui.panes;
+import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
public class ContactList extends MainContentList {
private Card card;
+ private List<Contact> contacts;
+ private String filter;
private List<String> formats = new LinkedList<String>();
private int selectedFormat = -1;
}
/**
- * Change the currently displayed contacts card.
+ * Change the currently displayed contacts card, only allowing those that
+ * satisfy the current filter.
*
* @param card
* the new {@link Card}
+ * @param filter
+ * the text filter or NULL for all contacts
*/
public void setCard(Card card) {
clearItems();
this.card = card;
+ this.contacts = new LinkedList<Contact>();
if (card != null) {
for (int i = 0; i < card.getContacts().size(); i++) {
- addItem("[contact line]");
+ Contact c = card.getContacts().get(i);
+ if (filter == null
+ || c.toString(format).toLowerCase()
+ .contains(filter.toLowerCase())) {
+ addItem("[contact line]");
+ contacts.add(c);
+ }
}
}
public void refreshData() {
int index = getSelectedIndex();
setCard(card);
+ if (index >= contacts.size())
+ index = contacts.size() - 1;
setSelectedIndex(index);
+
super.refreshData();
}
return getSelectedContact();
}
});
- actions.add(new KeyAction(Mode.DELETE_CONTACT, 'd',
+ actions.add(new KeyAction(Mode.ASK_USER_KEY, 'd',
Trans.StringId.KEY_ACTION_DELETE_CONTACT) {
@Override
public Object getObject() {
return getSelectedContact();
}
+
+ @Override
+ public String getQuestion() {
+ // TODO i18n
+ return "Delete contact? [Y/N]";
+ }
+
+ @Override
+ public String callback(String answer) {
+ if (answer.equalsIgnoreCase("y")) {
+ Contact contact = getSelectedContact();
+ if (contact != null && contact.delete()) {
+ return null;
+ }
+
+ // TODO i18n
+ return "Cannot delete contact";
+ }
+
+ return null;
+ }
});
- actions.add(new KeyAction(Mode.SAVE_CARD, 's',
+ actions.add(new KeyAction(Mode.ASK_USER_KEY, 's',
Trans.StringId.KEY_ACTION_SAVE_CARD) {
@Override
public Object getObject() {
return card;
}
+
+ @Override
+ public String getQuestion() {
+ return "Save changes? [Y/N]";
+ }
+
+ @Override
+ public String callback(String answer) {
+ if (answer.equalsIgnoreCase("y")) {
+ boolean ok = false;
+ try {
+ if (card != null && card.save())
+ ok = true;
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ }
+
+ if (!ok) {
+ return "Cannot save to file";
+ }
+ }
+
+ return null;
+ }
+
});
actions.add(new KeyAction(Mode.CONTACT_DETAILS, KeyType.Enter,
Trans.StringId.KEY_ACTION_VIEW_CONTACT) {
return false;
}
});
+ actions.add(new KeyAction(Mode.ASK_USER, 'w',
+ Trans.StringId.KEY_ACTION_SEARCH) {
+
+ @Override
+ public String getQuestion() {
+ return "Search:";
+ }
+
+ @Override
+ public String getDefaultAnswer() {
+ return filter;
+ }
+
+ @Override
+ public String callback(String answer) {
+ filter = answer;
+ setCard(card);
+ return null;
+ }
+ });
return actions;
}
@Override
public String getTitle() {
if (card != null) {
+ if (filter != null)
+ return card.getName() + " [" + filter + "]";
return card.getName();
}
List<TextPart> parts = new LinkedList<TextPart>();
Contact contact = null;
- if (index > -1 && index < card.size())
- contact = card.get(index);
+ if (index > -1 && index < contacts.size())
+ contact = contacts.get(index);
if (contact == null)
return parts;
*/
private Contact getSelectedContact() {
int index = getSelectedIndex();
- if (index > -1 && index < card.size())
- return card.get(index);
+ if (index > -1 && index < contacts.size())
+ return contacts.get(index);
return null;
}