From 176a83279a5aafb7e44cc7c34bf78f0bc35225fe Mon Sep 17 00:00:00 2001 From: Niki Roo Date: Sun, 6 Mar 2016 18:54:51 +0100 Subject: [PATCH] Beta2 relase Should not crash on unknown colour, 'edit' now in 'view' mode, not contact list anymore, can now edit/delete/add types, can set the group of a data, can delete a data and a contact, can add a new contact (UI still WIP) --- README.md | 4 +- src/be/nikiroo/jvcard/Card.java | 35 ++- src/be/nikiroo/jvcard/Contact.java | 53 +++- src/be/nikiroo/jvcard/Data.java | 70 +++++ .../jvcard/resources/colors.properties | 5 + src/be/nikiroo/jvcard/tui/Main.java | 2 +- src/be/nikiroo/jvcard/tui/UiColors.java | 15 +- .../jvcard/tui/panes/ContactDetails.java | 8 + .../jvcard/tui/panes/ContactDetailsRaw.java | 280 ++++++++++++++++-- .../nikiroo/jvcard/tui/panes/ContactList.java | 31 +- .../jvcard/tui/panes/MainContentList.java | 33 +++ 11 files changed, 473 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index 1d888ec..fa5b563 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Small TUI (text mode) VCard manager (also supports abook files) - it can create a Swing terminal or use a real terminal (try "--help") using Lanterna 3 (which can be found here on GitHub, too) - it will list all the contacts of the file you select - it will show more detailed informations about a selected contact, including an ASCII art representation of their photo if any -- it can delete a contact +- it can create/delete a contact - it can be used to edit your data (currently in RAW format, field by field) - it can save back to file - English and French versions available (will look for the host language, can be forced with "--lang en") @@ -15,6 +15,6 @@ Small TUI (text mode) VCard manager (also supports abook files) ## TODO - ".properties" files to easily change the colours -- correct EDIT support - customisation of VIEW_CONTACT - lot of other things +- correct UI for new contact/new data/edit data-types diff --git a/src/be/nikiroo/jvcard/Card.java b/src/be/nikiroo/jvcard/Card.java index 4c4e5d1..53ae972 100644 --- a/src/be/nikiroo/jvcard/Card.java +++ b/src/be/nikiroo/jvcard/Card.java @@ -86,6 +86,31 @@ public class Card { public Contact get(int index) { return contacts.get(index); } + + /** + * Add a new {@link Contact} in this {@link Card}. + * + * @param contact + * the new contact + */ + public void add(Contact contact) { + contact.setParent(this); + contact.setDirty(); + contacts.add(contact); + } + + /** + * Remove the given {@link Contact} from its this {@link Card} if it is in. + * + * @return TRUE in case of success + */ + public boolean remove(Contact contact) { + if (contacts.remove(contact)) { + setDirty(); + } + + return false; + } /** * Save the {@link Card} to the given {@link File} with the given @@ -196,16 +221,6 @@ public class Card { } } - /** - * Return the full list of {@link Contact}s. Please use responsibly (this is - * the original list). - * - * @return the list of {@link Contact}s - */ - List getContactsList() { - return contacts; - } - /** * Notify that this element has unsaved changes. */ diff --git a/src/be/nikiroo/jvcard/Contact.java b/src/be/nikiroo/jvcard/Contact.java index 3289939..c5579fe 100644 --- a/src/be/nikiroo/jvcard/Contact.java +++ b/src/be/nikiroo/jvcard/Contact.java @@ -34,15 +34,17 @@ public class Contact { boolean fn = false; boolean n = false; - for (Data data : content) { - if (data.getName().equals("N")) { - n = true; - } else if (data.getName().equals("FN")) { - fn = true; - } + if (content != null) { + for (Data data : content) { + if (data.getName().equals("N")) { + n = true; + } else if (data.getName().equals("FN")) { + fn = true; + } - if (!data.getName().equals("VERSION")) { - datas.add(data); + if (!data.getName().equals("VERSION")) { + datas.add(data); + } } } @@ -81,6 +83,32 @@ public class Contact { return datas.get(index); } + /** + * Add a new {@link Data} in this {@link Contact}. + * + * @param data + * the new data + */ + public void add(Data data) { + data.setParent(this); + data.setDirty(); + datas.add(data); + } + + /** + * Remove the given {@link Data} from its this {@link Contact} if it is in. + * + * @return TRUE in case of success + */ + public boolean remove(Data data) { + if (datas.remove(data)) { + setDirty(); + return true; + } + + return false; + } + /** * Return the preferred Data field with the given name, or NULL if none. * @@ -460,14 +488,7 @@ public class Contact { */ public boolean delete() { if (parent != null) { - List list = parent.getContactsList(); - for (int i = 0; i < list.size(); i++) { - if (list.get(i) == this) { - list.remove(i); - parent.setDirty(); - return true; - } - } + return parent.remove(this); } return false; diff --git a/src/be/nikiroo/jvcard/Data.java b/src/be/nikiroo/jvcard/Data.java index f2bb408..377c41e 100644 --- a/src/be/nikiroo/jvcard/Data.java +++ b/src/be/nikiroo/jvcard/Data.java @@ -85,6 +85,49 @@ public class Data { return types.get(index); } + /** + * Add a new {@link TypeInfo} in this {@link Data}. + * + * @param type + * the new type + */ + public void add(TypeInfo type) { + type.setParent(this); + type.setDirty(); + types.add(type); + } + + /** + * Remove the given {@link TypeInfo} from its this {@link Data} if it is in. + * + * @return TRUE in case of success + */ + public boolean remove(TypeInfo type) { + if (types.remove(type)) { + setDirty(); + } + + return false; + } + + /** + * Change the {@link TypeInfo}s of this {@link Data}. + * + * @param types + * the new types + */ + @Deprecated + public void setTypes(List types) { + // TODO: check if this method is required + this.types.clear(); + for (TypeInfo type : types) { + this.types.add(type); + type.setParent(this); + } + + setDirty(); + } + /** * Return the name of this {@link Data} * @@ -126,6 +169,20 @@ public class Data { return group; } + /** + * Change the group of this {@link Data} + * + * @param group + * the new group + */ + public void setGroup(String group) { + if ((group == null && this.group != null) + || (group != null && !group.equals(this.group))) { + this.group = group; + setDirty(); + } + } + /** * Return the bkey number of this {@link Data} or -1 if it is not binary. * @@ -164,6 +221,19 @@ public class Data { return b64 >= 0; } + /** + * Delete this {@link Contact} from its parent {@link Card} if any. + * + * @return TRUE in case of success + */ + public boolean delete() { + if (parent != null) { + return parent.remove(this); + } + + return false; + } + /** * Check if this {@link Data} has unsaved changes. * diff --git a/src/be/nikiroo/jvcard/resources/colors.properties b/src/be/nikiroo/jvcard/resources/colors.properties index 3fe44b7..0922478 100644 --- a/src/be/nikiroo/jvcard/resources/colors.properties +++ b/src/be/nikiroo/jvcard/resources/colors.properties @@ -44,3 +44,8 @@ VIEW_CONTACT_NORMAL_FG = WHITE VIEW_CONTACT_NORMAL_BG = BLACK VIEW_CONTACT_NOTES_TITLE_FG = BLACK VIEW_CONTACT_NOTES_TITLE_BG = WHITE +CONTACT_LINE_DIRTY_SELECTED_FG = BLACK +CONTACT_LINE_DIRTY_SELECTED_BG = WHITE +CONTACT_LINE_DIRTY_FG = BLACK +CONTACT_LINE_DIRTY_BG = WHITE + diff --git a/src/be/nikiroo/jvcard/tui/Main.java b/src/be/nikiroo/jvcard/tui/Main.java index 4c3f762..db6e522 100644 --- a/src/be/nikiroo/jvcard/tui/Main.java +++ b/src/be/nikiroo/jvcard/tui/Main.java @@ -24,7 +24,7 @@ import com.googlecode.lanterna.input.KeyStroke; */ public class Main { public static final String APPLICATION_TITLE = "jVcard"; - public static final String APPLICATION_VERSION = "1.0-beta2-dev"; + public static final String APPLICATION_VERSION = "1.0-beta2"; static private Trans transService; diff --git a/src/be/nikiroo/jvcard/tui/UiColors.java b/src/be/nikiroo/jvcard/tui/UiColors.java index a646dbe..5c5d01e 100644 --- a/src/be/nikiroo/jvcard/tui/UiColors.java +++ b/src/be/nikiroo/jvcard/tui/UiColors.java @@ -2,6 +2,7 @@ package be.nikiroo.jvcard.tui; import java.util.HashMap; import java.util.Map; +import java.util.MissingResourceException; import java.util.ResourceBundle; import be.nikiroo.jvcard.resources.Bundles; @@ -146,7 +147,12 @@ public class UiColors { */ private TextColor getBackgroundColor(Element el) { if (!colorMap.containsKey(el.name() + "_BG")) { - String value = bundle.getString(el.name() + "_BG"); + String value = null; + try { + value = bundle.getString(el.name() + "_BG"); + } catch (MissingResourceException mre) { + value = null; + } colorMap.put(el.name() + "_BG", convertToColor(value, TextColor.ANSI.BLACK)); } @@ -164,7 +170,12 @@ public class UiColors { */ private TextColor getForegroundColor(Element el) { if (!colorMap.containsKey(el.name() + "_FG")) { - String value = bundle.getString(el.name() + "_FG"); + String value = null; + try { + value = bundle.getString(el.name() + "_FG"); + } catch (MissingResourceException mre) { + value = null; + } colorMap.put(el.name() + "_FG", convertToColor(value, TextColor.ANSI.WHITE)); } diff --git a/src/be/nikiroo/jvcard/tui/panes/ContactDetails.java b/src/be/nikiroo/jvcard/tui/panes/ContactDetails.java index 08df403..2a69e41 100644 --- a/src/be/nikiroo/jvcard/tui/panes/ContactDetails.java +++ b/src/be/nikiroo/jvcard/tui/panes/ContactDetails.java @@ -166,6 +166,14 @@ public class ContactDetails extends MainContent { return false; } }); + // TODO: add "normal" edit and remove this one into RAW edit + actions.add(new KeyAction(Mode.CONTACT_DETAILS_RAW, 'e', + Trans.StringId.KEY_ACTION_EDIT_CONTACT) { + @Override + public Object getObject() { + return contact; + } + }); return actions; } diff --git a/src/be/nikiroo/jvcard/tui/panes/ContactDetailsRaw.java b/src/be/nikiroo/jvcard/tui/panes/ContactDetailsRaw.java index ec1a136..a81cec8 100644 --- a/src/be/nikiroo/jvcard/tui/panes/ContactDetailsRaw.java +++ b/src/be/nikiroo/jvcard/tui/panes/ContactDetailsRaw.java @@ -18,14 +18,14 @@ import com.googlecode.lanterna.input.KeyType; public class ContactDetailsRaw extends MainContentList { private Contact contact; - private int mode; + private boolean extMode; public ContactDetailsRaw(Contact contact) { this.contact = contact; - this.mode = 0; + this.extMode = false; for (int i = 0; i < contact.size(); i++) { - addItem("[detail line]"); + addItem("x"); } } @@ -44,7 +44,7 @@ public class ContactDetailsRaw extends MainContentList { Trans.StringId.DUMMY) { @Override public Object getObject() { - return contact.get(getSelectedIndex()); + return getSelectedData(); } @Override @@ -79,14 +79,153 @@ public class ContactDetailsRaw extends MainContentList { return "Cannot modify value"; } }); + actions.add(new KeyAction(Mode.ASK_USER_KEY, 'd', Trans.StringId.DUMMY) { + @Override + public Object getObject() { + return getSelectedData(); + } + + @Override + public String getQuestion() { + // TODO i18n + return "Delete data? [Y/N]"; + } + + @Override + public String callback(String answer) { + if (answer.equalsIgnoreCase("y")) { + Data data = getData(); + if (data != null && data.delete()) { + removeItem("x"); + return null; + } + + // TODO i18n + return "Cannot delete data"; + } + + return null; + } + }); + // TODO: ui + actions.add(new KeyAction(Mode.ASK_USER, 'a', Trans.StringId.DUMMY) { + @Override + public Object getObject() { + return contact; + } + + @Override + public String getQuestion() { + // TODO i18n + return "new data (xx = yy): "; + } + + @Override + public String callback(String answer) { + if (answer.length() > 0 && answer.contains("=")) { + String[] tab = answer.split("="); + Data data = new Data(null, tab[0].trim(), tab[1].trim(), + null); + getContact().add(data); + addItem("x"); + } + + return null; + } + }); + // TODO: use a real UI for this, not a simple text box (a list or + // something, maybe a whole new pane?) + actions.add(new KeyAction(Mode.ASK_USER, 't', Trans.StringId.DUMMY) { + private String previous; + + @Override + public Object getObject() { + return getSelectedData(); + } + + @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) { + previous = typesToString(data, null).toString(); + return previous; + } + + return null; + } + + @Override + public String callback(String answer) { + Data data = getData(); + if (data != null) { + if (!answer.equals(previous)) { + data.setTypes(stringToTypes(answer)); + } + return null; + } + + // TODO: i18n + return "Cannot modify value"; + } + }); + actions.add(new KeyAction(Mode.ASK_USER, 'g', Trans.StringId.DUMMY) { + private String previous; + + @Override + public Object getObject() { + return getSelectedData(); + } + + @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) { + previous = data.getGroup(); + return previous; + } + + return null; + } + + @Override + public String callback(String answer) { + Data data = getData(); + if (data != null) { + if (!answer.equals(previous)) { + data.setGroup(answer); + } + return null; + } + + // TODO: i18n + return "Cannot modify group"; + } + }); actions.add(new KeyAction(Mode.NONE, KeyType.Tab, Trans.StringId.KEY_ACTION_SWITCH_FORMAT) { @Override public boolean onAction() { - mode++; - if (mode > 1) - mode = 0; - + extMode = !extMode; return false; } }); @@ -118,6 +257,18 @@ public class ContactDetailsRaw extends MainContentList { boolean focused) { // TODO: from ini file? int SIZE_COL_1 = 15; + int SIZE_COL_2_OPT = 10; + + if (!extMode) + SIZE_COL_2_OPT = 0; + + List parts = new LinkedList(); + Data data = null; + if (index > -1 && index < contact.size()) + data = contact.get(index); + + if (data == null) + return parts; Element el = (focused && selected) ? Element.CONTACT_LINE_SELECTED : Element.CONTACT_LINE; @@ -126,9 +277,6 @@ public class ContactDetailsRaw extends MainContentList { Element elDirty = (focused && selected) ? Element.CONTACT_LINE_DIRTY_SELECTED : Element.CONTACT_LINE_DIRTY; - Data data = contact.get(index); - - List parts = new LinkedList(); if (data.isDirty()) { parts.add(new TextPart(" ", el)); parts.add(new TextPart("*", elDirty)); @@ -137,27 +285,22 @@ public class ContactDetailsRaw extends MainContentList { } String name = " " + data.getName() + " "; String value = null; + String group = null; StringBuilder valueBuilder = new StringBuilder(" "); - switch (mode) { - case 0: + if (!extMode) { valueBuilder.append(data.getValue()); if (data.getGroup() != null && data.getGroup().length() > 0) { valueBuilder.append("("); valueBuilder.append(data.getGroup()); valueBuilder.append(")"); } - break; - case 1: - for (int indexType = 0; indexType < data.size(); indexType++) { - TypeInfo type = data.get(indexType); - if (valueBuilder.length() > 1) - valueBuilder.append(", "); - valueBuilder.append(type.getName()); - valueBuilder.append(": "); - valueBuilder.append(type.getValue()); - } - break; + } else { + group = data.getGroup(); + if (group == null) + group = ""; + + typesToString(data, valueBuilder); } valueBuilder.append(" "); @@ -167,14 +310,99 @@ public class ContactDetailsRaw extends MainContentList { value = StringUtils.sanitize(value, UiColors.getInstance().isUnicode()); name = StringUtils.padString(name, SIZE_COL_1); + group = StringUtils.padString(group, SIZE_COL_2_OPT); value = StringUtils.padString(value, width - SIZE_COL_1 - - getSeparator().length() - 2); + - SIZE_COL_2_OPT - (extMode ? 2 : 1) * getSeparator().length() + - 2); parts.add(new TextPart(name, el)); parts.add(new TextPart(getSeparator(), elSep)); parts.add(new TextPart(value, el)); + if (extMode) { + parts.add(new TextPart(getSeparator(), elSep)); + parts.add(new TextPart(group, el)); + } return parts; - }; + } + + /** + * Return the currently selected {@link Data}. + * + * @return the currently selected {@link Data} + */ + private Data getSelectedData() { + int index = getSelectedIndex(); + if (index > -1 && index < this.contact.size()) + return contact.get(index); + return null; + } + + /** + * Serialise the {@link TypeInfo}s in the given {@link Data}. + * + * @param data + * the {@link Data} from which to take the {@link TypeInfo}s + * @param builder + * an optional {@link StringBuilder} to append the serialized + * version to + * + * @return the given {@link StringBuilder} or a new one if the given one is + * NULL + */ + static private StringBuilder typesToString(Data data, StringBuilder builder) { + if (builder == null) + builder = new StringBuilder(); + + for (int indexType = 0; indexType < data.size(); indexType++) { + TypeInfo type = data.get(indexType); + if (builder.length() > 1) + builder.append(", "); + builder.append(type.getName().replaceAll(",", "\\,")); + builder.append(": "); + builder.append(type.getValue().replaceAll(":", "\\:")); + } + + return builder; + } + /** + * Unserialise a list of {@link TypeInfo}s. + * + * @param value + * the serialised value + * + * @return the {@link TypeInfo} in their object form + */ + static private List stringToTypes(String value) { + List infos = new LinkedList(); + if (value == null || value.length() == 0) + return infos; + + char previous = '\0'; + char car = '\0'; + int done = 0; + for (int index = 0; index < value.length(); index++) { + car = value.charAt(index); + if (index == value.length() - 1) { + index++; + previous = '\0'; + car = ','; + } + + if (previous != '\\' && car == ',') { + String[] tab = value.substring(done, index).split("\\:"); + infos.add(new TypeInfo( // + tab[0].replaceAll("\\,", ",").replaceAll("\\:", ":") + .trim(), // + tab[1].replaceAll("\\,", ",").replaceAll("\\:", ":") + .trim())); + done = index + 1; + } + + previous = car; + } + + return infos; + } } diff --git a/src/be/nikiroo/jvcard/tui/panes/ContactList.java b/src/be/nikiroo/jvcard/tui/panes/ContactList.java index 6287a69..3cec44e 100644 --- a/src/be/nikiroo/jvcard/tui/panes/ContactList.java +++ b/src/be/nikiroo/jvcard/tui/panes/ContactList.java @@ -6,12 +6,13 @@ import java.util.List; import be.nikiroo.jvcard.Card; import be.nikiroo.jvcard.Contact; +import be.nikiroo.jvcard.Data; import be.nikiroo.jvcard.i18n.Trans; import be.nikiroo.jvcard.resources.Bundles; import be.nikiroo.jvcard.tui.KeyAction; -import be.nikiroo.jvcard.tui.UiColors; import be.nikiroo.jvcard.tui.KeyAction.DataType; import be.nikiroo.jvcard.tui.KeyAction.Mode; +import be.nikiroo.jvcard.tui.UiColors; import be.nikiroo.jvcard.tui.UiColors.Element; import com.googlecode.lanterna.input.KeyType; @@ -58,7 +59,7 @@ public class ContactList extends MainContentList { if (filter == null || c.toString(format).toLowerCase() .contains(filter.toLowerCase())) { - addItem("[contact line]"); + addItem("x"); contacts.add(c); } } @@ -91,12 +92,29 @@ public class ContactList extends MainContentList { public List getKeyBindings() { List actions = new LinkedList(); - // TODO add - actions.add(new KeyAction(Mode.CONTACT_DETAILS_RAW, 'e', - Trans.StringId.KEY_ACTION_EDIT_CONTACT) { + // TODO ui + actions.add(new KeyAction(Mode.ASK_USER, 'a', Trans.StringId.DUMMY) { @Override public Object getObject() { - return getSelectedContact(); + return card; + } + + @Override + public String getQuestion() { + // TODO i18n + return "new contact name: "; + } + + @Override + public String callback(String answer) { + if (answer.length() > 0) { + List datas = new LinkedList(); + datas.add(new Data(null, "FN", answer, null)); + getCard().add(new Contact(datas)); + addItem("x"); + } + + return null; } }); actions.add(new KeyAction(Mode.ASK_USER_KEY, 'd', @@ -117,6 +135,7 @@ public class ContactList extends MainContentList { if (answer.equalsIgnoreCase("y")) { Contact contact = getSelectedContact(); if (contact != null && contact.delete()) { + removeItem("x"); return null; } diff --git a/src/be/nikiroo/jvcard/tui/panes/MainContentList.java b/src/be/nikiroo/jvcard/tui/panes/MainContentList.java index e1358ef..4b6d8ad 100644 --- a/src/be/nikiroo/jvcard/tui/panes/MainContentList.java +++ b/src/be/nikiroo/jvcard/tui/panes/MainContentList.java @@ -125,6 +125,39 @@ abstract public class MainContentList extends MainContent implements Runnable { lines.addItem(line, this); } + /** + * Delete the given item. + * + * Remark: it will only delete the first found instance if multiple + * instances of this item are present. + * + * @param line + * the line to delete + * + * @return TRUE if the item was deleted + */ + public boolean removeItem(String line) { + boolean deleted = false; + + List copy = lines.getItems(); + for (int index = 0; index < copy.size(); index++) { + if (copy.get(index).toString().equals(line)) { + deleted = true; + copy.remove(index); + break; + } + } + + int index = getSelectedIndex(); + clearItems(); + for (Runnable run : copy) { + addItem(run.toString()); + } + setSelectedIndex(index); + + return deleted; + } + /** * Clear all the items in this {@link MainContentList} */ -- 2.27.0