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