KeyAction management now more generic
authorNiki Roo <niki@nikiroo.be>
Tue, 1 Mar 2016 12:45:14 +0000 (13:45 +0100)
committerNiki Roo <niki@nikiroo.be>
Tue, 1 Mar 2016 12:45:14 +0000 (13:45 +0100)
src/be/nikiroo/jvcard/i18n/Trans.java
src/be/nikiroo/jvcard/tui/ImageText.java
src/be/nikiroo/jvcard/tui/ImageTextControl.java
src/be/nikiroo/jvcard/tui/KeyAction.java
src/be/nikiroo/jvcard/tui/MainWindow.java
src/be/nikiroo/jvcard/tui/panes/ContactDetails.java
src/be/nikiroo/jvcard/tui/panes/ContactDetailsRaw.java
src/be/nikiroo/jvcard/tui/panes/ContactList.java

index 069f81ca90bf6bb6299870e04da915c52059717b..0a06b573f5d188bcd101676584c10a945f649028 100644 (file)
@@ -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");
        }
 }
index b0232f7b798d023bba7fe0ef8f18f0dd00da7225..073315de8c499d08a12f01faaa63fa89c30ae84e 100644 (file)
@@ -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()
index 4ee35e97ba34bb95e8c1b1b7b9dafcde56edca43..33773b2dc280a2962e1d66c2312ca827d9621143 100644 (file)
@@ -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);
                }
        }
index a95835eb28b77468b758b6b53516bf0e481bda7f..b60664b8cb479c1b89ffaa4a9d0537dbf33b589e 100644 (file)
@@ -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;
+       }
 }
index cda484da6d30ecf2063101308c69e082d0adebc1..24e97e841f35e54db30bdda139e03cd56b1855ad 100644 (file)
@@ -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:
index 5cfce87d9d25d93276bf0d78b878025b78c405c9..b2bb5626c91e5296ce9962f65051b8c36b1812bd 100644 (file)
@@ -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;
+       }
 }
index 271be442e82922e1ef30c0bd7dad61ee1ab3d5b1..1fc83c7765be41644c5e506014cdc0f86d0168fe 100644 (file)
@@ -98,18 +98,51 @@ public class ContactDetailsRaw extends MainContentList {
        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) {
index 3bb6eac7095b093b006a3b4f865ce88905d749e3..078d38e89438a038c33d92513a34b5b4d521f6ae 100644 (file)
@@ -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<Contact> contacts;
+       private String filter;
 
        private List<String> formats = new LinkedList<String>();
        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<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);
+                               }
                        }
                }
 
@@ -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<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;
@@ -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;
        }