From ae22c2473f7203b8713dec1c1de532c312000d1e Mon Sep 17 00:00:00 2001 From: Niki Roo Date: Tue, 1 Mar 2016 13:45:14 +0100 Subject: [PATCH] KeyAction management now more generic --- src/be/nikiroo/jvcard/i18n/Trans.java | 7 +- src/be/nikiroo/jvcard/tui/ImageText.java | 58 ++++++---- .../nikiroo/jvcard/tui/ImageTextControl.java | 66 +++++++++--- src/be/nikiroo/jvcard/tui/KeyAction.java | 34 +++++- src/be/nikiroo/jvcard/tui/MainWindow.java | 78 ++++---------- .../jvcard/tui/panes/ContactDetails.java | 85 +++++++++++++-- .../jvcard/tui/panes/ContactDetailsRaw.java | 37 ++++++- .../nikiroo/jvcard/tui/panes/ContactList.java | 100 ++++++++++++++++-- 8 files changed, 352 insertions(+), 113 deletions(-) diff --git a/src/be/nikiroo/jvcard/i18n/Trans.java b/src/be/nikiroo/jvcard/i18n/Trans.java index 069f81c..0a06b57 100644 --- a/src/be/nikiroo/jvcard/i18n/Trans.java +++ b/src/be/nikiroo/jvcard/i18n/Trans.java @@ -30,8 +30,10 @@ public class Trans { 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() { @@ -139,5 +141,8 @@ public class 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"); } } diff --git a/src/be/nikiroo/jvcard/tui/ImageText.java b/src/be/nikiroo/jvcard/tui/ImageText.java index b0232f7..073315d 100644 --- a/src/be/nikiroo/jvcard/tui/ImageText.java +++ b/src/be/nikiroo/jvcard/tui/ImageText.java @@ -23,6 +23,13 @@ public class ImageText { 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 @@ -54,6 +61,19 @@ public class ImageText { 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. * @@ -63,10 +83,14 @@ public class ImageText { * 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); } /** @@ -76,34 +100,21 @@ public class ImageText { * 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; - } } /** @@ -146,7 +157,8 @@ public class ImageText { */ public String getText() { if (text == null) { - if (image == null) + if (image == null || size == null || size.getColumns() == 0 + || size.getRows() == 0) return ""; int mult = 1; @@ -167,7 +179,7 @@ public class ImageText { 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() diff --git a/src/be/nikiroo/jvcard/tui/ImageTextControl.java b/src/be/nikiroo/jvcard/tui/ImageTextControl.java index 4ee35e9..33773b2 100644 --- a/src/be/nikiroo/jvcard/tui/ImageTextControl.java +++ b/src/be/nikiroo/jvcard/tui/ImageTextControl.java @@ -9,11 +9,26 @@ import com.googlecode.lanterna.gui2.BorderLayout; 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()) { @@ -27,11 +42,17 @@ public class ImageTextControl extends Panel { } 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(); @@ -39,25 +60,46 @@ public class ImageTextControl extends Panel { 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); } } diff --git a/src/be/nikiroo/jvcard/tui/KeyAction.java b/src/be/nikiroo/jvcard/tui/KeyAction.java index a95835e..b60664b 100644 --- a/src/be/nikiroo/jvcard/tui/KeyAction.java +++ b/src/be/nikiroo/jvcard/tui/KeyAction.java @@ -27,7 +27,7 @@ public class KeyAction { * */ 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 { @@ -161,4 +161,36 @@ public class KeyAction { 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; + } } diff --git a/src/be/nikiroo/jvcard/tui/MainWindow.java b/src/be/nikiroo/jvcard/tui/MainWindow.java index cda484d..24e97e8 100644 --- a/src/be/nikiroo/jvcard/tui/MainWindow.java +++ b/src/be/nikiroo/jvcard/tui/MainWindow.java @@ -474,6 +474,9 @@ public class MainWindow extends BasicWindow { 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 { @@ -542,10 +545,6 @@ public class MainWindow extends BasicWindow { 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; @@ -569,18 +568,18 @@ public class MainWindow extends BasicWindow { 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: @@ -615,58 +614,25 @@ public class MainWindow extends BasicWindow { 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: diff --git a/src/be/nikiroo/jvcard/tui/panes/ContactDetails.java b/src/be/nikiroo/jvcard/tui/panes/ContactDetails.java index 5cfce87..b2bb562 100644 --- a/src/be/nikiroo/jvcard/tui/panes/ContactDetails.java +++ b/src/be/nikiroo/jvcard/tui/panes/ContactDetails.java @@ -23,7 +23,10 @@ import com.googlecode.lanterna.input.KeyType; 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; @@ -31,7 +34,15 @@ public class ContactDetails extends MainContent { 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) { @@ -49,19 +60,13 @@ public class ContactDetails extends MainContent { 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 @@ -86,7 +91,7 @@ public class ContactDetails extends MainContent { } }); actions.add(new KeyAction(Mode.NONE, 'i', - Trans.StringId.DUMMY) { + Trans.StringId.KEY_ACTION_INVERT) { @Override public boolean onAction() { if (txt != null) { @@ -96,7 +101,67 @@ public class ContactDetails extends MainContent { 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; + } } diff --git a/src/be/nikiroo/jvcard/tui/panes/ContactDetailsRaw.java b/src/be/nikiroo/jvcard/tui/panes/ContactDetailsRaw.java index 271be44..1fc83c7 100644 --- a/src/be/nikiroo/jvcard/tui/panes/ContactDetailsRaw.java +++ b/src/be/nikiroo/jvcard/tui/panes/ContactDetailsRaw.java @@ -98,18 +98,51 @@ public class ContactDetailsRaw extends MainContentList { public DataType getDataType() { return DataType.DATA; } - + @Override public List getKeyBindings() { // TODO Auto-generated method stub List actions = new LinkedList(); // 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) { diff --git a/src/be/nikiroo/jvcard/tui/panes/ContactList.java b/src/be/nikiroo/jvcard/tui/panes/ContactList.java index 3bb6eac..078d38e 100644 --- a/src/be/nikiroo/jvcard/tui/panes/ContactList.java +++ b/src/be/nikiroo/jvcard/tui/panes/ContactList.java @@ -1,5 +1,6 @@ package be.nikiroo.jvcard.tui.panes; +import java.io.IOException; import java.util.LinkedList; import java.util.List; @@ -16,6 +17,8 @@ import com.googlecode.lanterna.input.KeyType; public class ContactList extends MainContentList { private Card card; + private List contacts; + private String filter; private List formats = new LinkedList(); private int selectedFormat = -1; @@ -34,18 +37,28 @@ public class ContactList extends MainContentList { } /** - * 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(); 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); + } } } @@ -56,7 +69,10 @@ public class ContactList extends MainContentList { public void refreshData() { int index = getSelectedIndex(); setCard(card); + if (index >= contacts.size()) + index = contacts.size() - 1; setSelectedIndex(index); + super.refreshData(); } @@ -81,19 +97,65 @@ public class ContactList extends MainContentList { 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) { @@ -110,6 +172,26 @@ public class ContactList extends MainContentList { 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; } @@ -122,6 +204,8 @@ public class ContactList extends MainContentList { @Override public String getTitle() { if (card != null) { + if (filter != null) + return card.getName() + " [" + filter + "]"; return card.getName(); } @@ -134,8 +218,8 @@ public class ContactList extends MainContentList { List parts = new LinkedList(); 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; @@ -175,8 +259,8 @@ public class ContactList extends MainContentList { */ 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; } -- 2.27.0