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