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