4cbbdf7d38e2e27a6264880123c647d3fd2f7410
[jvcard.git] / src / be / nikiroo / jvcard / tui / panes / ContactDetails.java
1 package be.nikiroo.jvcard.tui.panes;
2
3 import java.awt.Image;
4 import java.io.ByteArrayInputStream;
5 import java.util.LinkedList;
6 import java.util.List;
7 import java.util.MissingResourceException;
8 import java.util.ResourceBundle;
9
10 import javax.imageio.ImageIO;
11 import javax.xml.bind.DatatypeConverter;
12
13 import be.nikiroo.jvcard.Contact;
14 import be.nikiroo.jvcard.Data;
15 import be.nikiroo.jvcard.TypeInfo;
16 import be.nikiroo.jvcard.resources.Bundles;
17 import be.nikiroo.jvcard.resources.StringUtils;
18 import be.nikiroo.jvcard.resources.Trans;
19 import be.nikiroo.jvcard.tui.ImageTextControl;
20 import be.nikiroo.jvcard.tui.KeyAction;
21 import be.nikiroo.jvcard.tui.KeyAction.DataType;
22 import be.nikiroo.jvcard.tui.KeyAction.Mode;
23 import be.nikiroo.jvcard.tui.UiColors;
24
25 import com.googlecode.lanterna.TerminalSize;
26 import com.googlecode.lanterna.gui2.BorderLayout;
27 import com.googlecode.lanterna.gui2.Borders;
28 import com.googlecode.lanterna.gui2.Direction;
29 import com.googlecode.lanterna.gui2.Label;
30 import com.googlecode.lanterna.gui2.LinearLayout;
31 import com.googlecode.lanterna.gui2.Panel;
32 import com.googlecode.lanterna.input.KeyType;
33
34 public class ContactDetails extends MainContent {
35 private Contact contact;
36 private Panel top;
37 private ImageTextControl txtImage;
38 private Image image;
39 private boolean fullscreenImage;
40 private Panel infoPanel;
41 private Label note;
42 ResourceBundle map;
43
44 public ContactDetails(Contact contact) {
45 map = Bundles.getBundle("display");
46
47 BorderLayout blayout = new BorderLayout();
48 setLayoutManager(blayout);
49
50 top = new Panel();
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
66 setContact(contact);
67
68 addComponent(top.withBorder(Borders.doubleLineBevel()),
69 BorderLayout.Location.TOP);
70 addComponent(notePanel.withBorder(Borders.singleLineBevel()),
71 BorderLayout.Location.CENTER);
72 }
73
74 /**
75 * Change the enclosed {@link Contact} from this {@link ContactDetails}.
76 * Also re-set the image.
77 *
78 * @param contact
79 * the new {@link Contact}
80 */
81 public void setContact(Contact contact) {
82 this.contact = contact;
83 image = null;
84
85 if (contact != null) {
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
94 infoPanel.addComponent(UiColors.Element.VIEW_CONTACT_NAME
95 .createLabel(name));
96 infoPanel.addComponent(UiColors.Element.VIEW_CONTACT_NORMAL
97 .createLabel(""));
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()) {
154 infoPanel
155 .addComponent(el.createLabel(StringUtils
156 .padString(label, labelSize)
157 + contact
158 .getPreferredDataValue(field)));
159 } else {
160 infoPanel
161 .addComponent(UiColors.Element.VIEW_CONTACT_NORMAL.createLabel(StringUtils
162 .padString(label, labelSize)
163 + contact
164 .getPreferredDataValue(field)));
165 }
166 }
167 } else {
168 infoPanel.addComponent(el.createLabel(StringUtils
169 .padString(label, labelSize)
170 + contact.getPreferredDataValue(field)));
171 }
172 } else {
173 String label = info;
174 infoPanel.addComponent(UiColors.Element.VIEW_CONTACT_NORMAL
175 .createLabel(StringUtils
176 .padString(label, labelSize)));
177 }
178 }
179 // end of list
180
181 infoPanel.addComponent(UiColors.Element.VIEW_CONTACT_NORMAL
182 .createLabel(""));
183
184 String notes = contact.getPreferredDataValue("NOTE");
185 if (notes == null)
186 notes = "";
187 note.setText(notes.replaceAll("\\\\n", "\n"));
188
189 Data photo = contact.getPreferredData("PHOTO");
190 if (photo != null) {
191 TypeInfo encoding = null;
192 for (int index = 0; index < photo.size(); index++) {
193 TypeInfo info = photo.get(index);
194 if (info.getName() != null) {
195 if (info.getName().equalsIgnoreCase("ENCODING"))
196 encoding = info;
197 // We don't check for the "TYPE" anymore, we just defer
198 // it to ImageIcon
199 }
200 }
201
202 if (encoding != null && encoding.getValue() != null
203 && encoding.getValue().equalsIgnoreCase("b")) {
204
205 try {
206 image = ImageIO.read(new ByteArrayInputStream(
207 DatatypeConverter.parseBase64Binary(photo
208 .getValue())));
209 image.toString();
210 } catch (Exception e) {
211 System.err.println("Cannot parse image for contact: "
212 + contact.getPreferredDataValue("UID"));
213 }
214 }
215 }
216 }
217
218 setImage(image);
219 }
220
221 @Override
222 public DataType getDataType() {
223 return DataType.DATA;
224 }
225
226 @Override
227 public List<KeyAction> getKeyBindings() {
228 List<KeyAction> actions = new LinkedList<KeyAction>();
229
230 actions.add(new KeyAction(Mode.NONE, KeyType.Tab,
231 Trans.StringId.KEY_ACTION_SWITCH_FORMAT) {
232 @Override
233 public boolean onAction() {
234 if (txtImage != null) {
235 txtImage.switchMode();
236 }
237
238 return false;
239 }
240 });
241 actions.add(new KeyAction(Mode.NONE, 'i',
242 Trans.StringId.KEY_ACTION_INVERT) {
243 @Override
244 public boolean onAction() {
245 if (txtImage != null) {
246 txtImage.invertColor();
247 }
248
249 return false;
250 }
251 });
252 actions.add(new KeyAction(Mode.NONE, 'f',
253 Trans.StringId.KEY_ACTION_FULLSCREEN) {
254 @Override
255 public boolean onAction() {
256 fullscreenImage = !fullscreenImage;
257 setImage(image);
258 return false;
259 }
260 });
261 // TODO: add "normal" edit
262 actions.add(new KeyAction(Mode.CONTACT_DETAILS_RAW, 'r',
263 Trans.StringId.KEY_ACTION_EDIT_CONTACT_RAW) {
264 @Override
265 public Object getObject() {
266 return contact;
267 }
268 });
269
270 return actions;
271 }
272
273 @Override
274 public synchronized Panel setSize(TerminalSize size) {
275 super.setSize(size);
276 setImage(image);
277 return this;
278 }
279
280 /**
281 * Set the {@link Image} to render and refresh it to the current size
282 * constraints.
283 *
284 * @param image
285 * the new {@link Image}
286 */
287 private void setImage(Image image) {
288 this.image = image;
289
290 if (txtImage != null && top.containsComponent(txtImage))
291 top.removeComponent(txtImage);
292
293 TerminalSize size = getTxtSize();
294 if (size != null) {
295 if (txtImage != null)
296 txtImage.setSize(size);
297 else
298 txtImage = new ImageTextControl(image, size);
299 }
300
301 if (size != null) {
302 top.addComponent(txtImage, BorderLayout.Location.LEFT);
303 }
304
305 invalidate();
306 }
307
308 /**
309 * Compute the size to use for the {@link Image} text rendering. Return NULL
310 * in case of error.
311 *
312 * @return the {@link TerminalSize} to use or NULL if it is not possible
313 */
314 private TerminalSize getTxtSize() {
315 if (image != null && getSize() != null && getSize().getColumns() > 0
316 && getSize().getRows() > 0) {
317 if (fullscreenImage) {
318 return getSize();
319 } else {
320 // TODO: configure size?
321 int w = getSize().getColumns() - 40;
322 int h = getSize().getRows() - 5;
323 if (w <= 0 || h <= 0)
324 return null;
325
326 return new TerminalSize(w, h);
327 }
328 }
329
330 return null;
331 }
332 }