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