VCard format: correctly co/decode escaped values
[jvcard.git] / src / be / nikiroo / jvcard / tui / panes / ContactDetails.java
CommitLineData
f04d8b1c
NR
1package be.nikiroo.jvcard.tui.panes;
2
3import java.awt.Image;
1c03abaf 4import java.io.ByteArrayInputStream;
25232463 5import java.util.LinkedList;
f04d8b1c 6import java.util.List;
3634193b
NR
7import java.util.MissingResourceException;
8import java.util.ResourceBundle;
f04d8b1c 9
1c03abaf 10import javax.imageio.ImageIO;
6b6a62ca 11import javax.xml.bind.DatatypeConverter;
f04d8b1c
NR
12
13import be.nikiroo.jvcard.Contact;
14import be.nikiroo.jvcard.Data;
15import be.nikiroo.jvcard.TypeInfo;
3634193b
NR
16import be.nikiroo.jvcard.resources.Bundles;
17import be.nikiroo.jvcard.resources.StringUtils;
7da41ecd 18import be.nikiroo.jvcard.resources.Trans;
25232463 19import be.nikiroo.jvcard.tui.ImageTextControl;
f04d8b1c
NR
20import be.nikiroo.jvcard.tui.KeyAction;
21import be.nikiroo.jvcard.tui.KeyAction.DataType;
25232463 22import be.nikiroo.jvcard.tui.KeyAction.Mode;
f82bad11 23import be.nikiroo.jvcard.tui.UiColors;
f04d8b1c
NR
24
25import com.googlecode.lanterna.TerminalSize;
26import com.googlecode.lanterna.gui2.BorderLayout;
3634193b 27import com.googlecode.lanterna.gui2.Borders;
f82bad11
NR
28import com.googlecode.lanterna.gui2.Direction;
29import com.googlecode.lanterna.gui2.Label;
30import com.googlecode.lanterna.gui2.LinearLayout;
f04d8b1c 31import com.googlecode.lanterna.gui2.Panel;
25232463 32import com.googlecode.lanterna.input.KeyType;
f04d8b1c
NR
33
34public class ContactDetails extends MainContent {
35 private Contact contact;
ae22c247 36 private Panel top;
f82bad11 37 private ImageTextControl txtImage;
ae22c247
NR
38 private Image image;
39 private boolean fullscreenImage;
f82bad11
NR
40 private Panel infoPanel;
41 private Label note;
3634193b 42 ResourceBundle map;
f04d8b1c
NR
43
44 public ContactDetails(Contact contact) {
3634193b
NR
45 map = Bundles.getBundle("display");
46
f04d8b1c
NR
47 BorderLayout blayout = new BorderLayout();
48 setLayoutManager(blayout);
49
ae22c247 50 top = new Panel();
f82bad11
NR
51 blayout = new BorderLayout();
52 top.setLayoutManager(blayout);
53
54 infoPanel = new Panel();
55 infoPanel.setLayoutManager(new LinearLayout(Direction.VERTICAL));
56 top.addComponent(infoPanel, BorderLayout.Location.CENTER);
57
58 Panel notePanel = new Panel();
59 notePanel.setLayoutManager(new LinearLayout(Direction.HORIZONTAL));
60
61 notePanel.addComponent(UiColors.Element.VIEW_CONTACT_NOTES_TITLE
62 .createLabel("Notes:"));
63 note = UiColors.Element.VIEW_CONTACT_NORMAL.createLabel("");
64 notePanel.addComponent(note);
65
ae22c247 66 setContact(contact);
f82bad11 67
3634193b
NR
68 addComponent(top.withBorder(Borders.doubleLineBevel()),
69 BorderLayout.Location.TOP);
70 addComponent(notePanel.withBorder(Borders.singleLineBevel()),
71 BorderLayout.Location.CENTER);
ae22c247
NR
72 }
73
f82bad11
NR
74 /**
75 * Change the enclosed {@link Contact} from this {@link ContactDetails}.
2a96e7b2 76 * Also re-set the image.
f82bad11
NR
77 *
78 * @param contact
79 * the new {@link Contact}
80 */
ae22c247 81 public void setContact(Contact contact) {
ae22c247 82 this.contact = contact;
2a96e7b2 83 image = null;
ae22c247 84
2a96e7b2 85 if (contact != null) {
f82bad11
NR
86 infoPanel.removeAllComponents();
87
88 String name = contact.getPreferredDataValue("FN");
89 if (name == null || name.length() == 0) {
90 // TODO format it ourself
91 name = contact.getPreferredDataValue("N");
92 }
93
f82bad11
NR
94 infoPanel.addComponent(UiColors.Element.VIEW_CONTACT_NAME
95 .createLabel(name));
f82bad11
NR
96 infoPanel.addComponent(UiColors.Element.VIEW_CONTACT_NORMAL
97 .createLabel(""));
3634193b
NR
98
99 // List of infos:
100 int labelSize = -1;
101 try {
102 labelSize = Integer.parseInt(map
103 .getString("CONTACT_DETAILS_LABEL_WIDTH"));
104
105 } catch (NumberFormatException e) {
106 e.printStackTrace();
107 labelSize = -1;
108 } catch (MissingResourceException e) {
109 labelSize = -1;
110 }
111
112 String infoFormat = "";
113 try {
114 infoFormat = map.getString("CONTACT_DETAILS_INFO");
115 } catch (MissingResourceException e) {
116 e.printStackTrace();
117 }
118
119 String[] infos = infoFormat.split("\\|");
120 for (String info : infos) {
121 // # - "=FIELD" will take the preferred value for this field
122 // # - "+FIELD" will take the preferred value for this field and
123 // highlight it
124 // # - "#FIELD" will take all the values with this field's name
125 // # - "*FIELD" will take all the values with this field's name,
126 // highlighting the preferred one
127 // #
128
129 boolean hl = false;
130 boolean all = false;
131 if (info.contains("+") || info.contains("#"))
132 hl = true;
133 if (info.contains("*") || info.contains("#"))
134 all = true;
135
136 if (all || hl || info.contains("=")) {
137 UiColors.Element el = hl ? UiColors.Element.VIEW_CONTACT_HIGHLIGHT
138 : UiColors.Element.VIEW_CONTACT_NORMAL;
139
140 int index = info.indexOf('=');
141 if (index < 0)
142 index = info.indexOf('+');
143 if (index < 0)
144 index = info.indexOf('#');
145 if (index < 0)
146 index = info.indexOf('*');
147
148 String label = info.substring(0, index);
149 String field = info.substring(index + 1);
150
151 if (all) {
152 for (Data data : contact.getData(field)) {
153 if (data.isPreferred()) {
aecb3399
NR
154 infoPanel.addComponent(el
155 .createLabel(StringUtils.padString(
156 label, labelSize)
157 + data.toString()));
3634193b
NR
158 } else {
159 infoPanel
aecb3399
NR
160 .addComponent(UiColors.Element.VIEW_CONTACT_NORMAL
161 .createLabel(StringUtils
162 .padString(label,
163 labelSize)
164 + data.toString()));
3634193b
NR
165 }
166 }
167 } else {
aecb3399
NR
168 String val = contact.getPreferredDataValue(field);
169 if (val == null)
170 val = "";
3634193b 171 infoPanel.addComponent(el.createLabel(StringUtils
aecb3399 172 .padString(label, labelSize) + val));
3634193b
NR
173 }
174 } else {
175 String label = info;
176 infoPanel.addComponent(UiColors.Element.VIEW_CONTACT_NORMAL
177 .createLabel(StringUtils
178 .padString(label, labelSize)));
179 }
180 }
181 // end of list
182
f82bad11
NR
183 infoPanel.addComponent(UiColors.Element.VIEW_CONTACT_NORMAL
184 .createLabel(""));
185
186 String notes = contact.getPreferredDataValue("NOTE");
187 if (notes == null)
188 notes = "";
aecb3399 189 note.setText(notes);
f82bad11 190
f04d8b1c
NR
191 Data photo = contact.getPreferredData("PHOTO");
192 if (photo != null) {
193 TypeInfo encoding = null;
78e4af97
NR
194 for (int index = 0; index < photo.size(); index++) {
195 TypeInfo info = photo.get(index);
f04d8b1c
NR
196 if (info.getName() != null) {
197 if (info.getName().equalsIgnoreCase("ENCODING"))
198 encoding = info;
2a96e7b2
NR
199 // We don't check for the "TYPE" anymore, we just defer
200 // it to ImageIcon
f04d8b1c
NR
201 }
202 }
203
204 if (encoding != null && encoding.getValue() != null
205 && encoding.getValue().equalsIgnoreCase("b")) {
206
1c03abaf
NR
207 try {
208 image = ImageIO.read(new ByteArrayInputStream(
209 DatatypeConverter.parseBase64Binary(photo
210 .getValue())));
211 image.toString();
212 } catch (Exception e) {
213 System.err.println("Cannot parse image for contact: "
214 + contact.getPreferredDataValue("UID"));
215 }
f04d8b1c
NR
216 }
217 }
218 }
219
f82bad11 220 setImage(image);
f04d8b1c 221 }
25232463
NR
222
223 @Override
224 public DataType getDataType() {
225 return DataType.DATA;
226 }
227
228 @Override
229 public List<KeyAction> getKeyBindings() {
230 List<KeyAction> actions = new LinkedList<KeyAction>();
231
25232463
NR
232 actions.add(new KeyAction(Mode.NONE, KeyType.Tab,
233 Trans.StringId.KEY_ACTION_SWITCH_FORMAT) {
234 @Override
235 public boolean onAction() {
f82bad11
NR
236 if (txtImage != null) {
237 txtImage.switchMode();
25232463
NR
238 }
239
240 return false;
241 }
242 });
243 actions.add(new KeyAction(Mode.NONE, 'i',
ae22c247 244 Trans.StringId.KEY_ACTION_INVERT) {
25232463
NR
245 @Override
246 public boolean onAction() {
f82bad11
NR
247 if (txtImage != null) {
248 txtImage.invertColor();
25232463
NR
249 }
250
251 return false;
252 }
253 });
ae22c247
NR
254 actions.add(new KeyAction(Mode.NONE, 'f',
255 Trans.StringId.KEY_ACTION_FULLSCREEN) {
256 @Override
257 public boolean onAction() {
258 fullscreenImage = !fullscreenImage;
259 setImage(image);
260 return false;
261 }
262 });
3634193b
NR
263 // TODO: add "normal" edit
264 actions.add(new KeyAction(Mode.CONTACT_DETAILS_RAW, 'r',
265 Trans.StringId.KEY_ACTION_EDIT_CONTACT_RAW) {
176a8327
NR
266 @Override
267 public Object getObject() {
268 return contact;
269 }
270 });
25232463
NR
271
272 return actions;
273 }
ae22c247
NR
274
275 @Override
276 public synchronized Panel setSize(TerminalSize size) {
277 super.setSize(size);
278 setImage(image);
279 return this;
280 }
281
282 /**
f82bad11
NR
283 * Set the {@link Image} to render and refresh it to the current size
284 * constraints.
ae22c247
NR
285 *
286 * @param image
287 * the new {@link Image}
288 */
289 private void setImage(Image image) {
290 this.image = image;
291
f82bad11
NR
292 if (txtImage != null && top.containsComponent(txtImage))
293 top.removeComponent(txtImage);
294
ae22c247
NR
295 TerminalSize size = getTxtSize();
296 if (size != null) {
f82bad11
NR
297 if (txtImage != null)
298 txtImage.setSize(size);
ae22c247 299 else
f82bad11 300 txtImage = new ImageTextControl(image, size);
ae22c247
NR
301 }
302
f82bad11
NR
303 if (size != null) {
304 top.addComponent(txtImage, BorderLayout.Location.LEFT);
305 }
ae22c247 306
f82bad11 307 invalidate();
ae22c247
NR
308 }
309
310 /**
311 * Compute the size to use for the {@link Image} text rendering. Return NULL
312 * in case of error.
313 *
314 * @return the {@link TerminalSize} to use or NULL if it is not possible
315 */
316 private TerminalSize getTxtSize() {
317 if (image != null && getSize() != null && getSize().getColumns() > 0
318 && getSize().getRows() > 0) {
319 if (fullscreenImage) {
320 return getSize();
321 } else {
f82bad11
NR
322 // TODO: configure size?
323 int w = getSize().getColumns() - 40;
aecb3399 324 int h = getSize().getRows() - 9;
f82bad11
NR
325 if (w <= 0 || h <= 0)
326 return null;
327
328 return new TerminalSize(w, h);
ae22c247
NR
329 }
330 }
331
332 return null;
333 }
f04d8b1c 334}