extend the text editor plugin to be able to modify the .colors file
[Rockbox.git] / apps / plugins / text_editor.c
blob943596905c6b87d98e1242a167dd32b49ae3ae1a
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2006 Jonathan Gordon
12 * All files in this archive are subject to the GNU General Public License.
13 * See the file COPYING in the source tree root for full license agreement.
15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * KIND, either express or implied.
18 ****************************************************************************/
19 #include "plugin.h"
20 #include "action.h"
21 #include "oldmenuapi.h"
23 #if PLUGIN_BUFFER_SIZE > 0x45000
24 #define MAX_CHARS 0x40000 /* 128 kiB */
25 #else
26 #define MAX_CHARS 0x6000 /* 24 kiB */
27 #endif
28 #define MAX_LINE_LEN 2048
29 PLUGIN_HEADER
30 static struct plugin_api* rb;
32 static char buffer[MAX_CHARS];
33 static char eol[3];
34 static int char_count = 0;
35 static int line_count = 0;
36 static int last_action_line = 0;
37 static int last_char_index = 0;
39 #define ACTION_INSERT 0
40 #define ACTION_GET 1
41 #define ACTION_REMOVE 2
42 #define ACTION_UPDATE 3
43 #define ACTION_CONCAT 4
45 int _do_action(int action, char* str, int line);
46 #ifndef HAVE_ADJUSTABLE_CPU_FREQ
47 #define do_action _do_action
48 #else
49 int do_action(int action, char* str, int line)
51 int r;
52 rb->cpu_boost(1);
53 r = _do_action(action,str,line);
54 rb->cpu_boost(0);
55 return r;
57 #endif
59 int _do_action(int action, char* str, int line)
61 int len;
62 int i=0,c=0;
63 if (line>=last_action_line)
65 i = last_action_line;
66 c = last_char_index;
68 while (i<line && i<line_count)
70 c += rb->strlen(&buffer[c])+1;
71 i++;
73 switch (action)
75 case ACTION_INSERT:
76 len = rb->strlen(str)+1;
77 if ( char_count+ len > MAX_CHARS )
78 return 0;
79 rb->memmove(&buffer[c+len],&buffer[c],char_count);
80 rb->strcpy(&buffer[c],str);
81 char_count += len;
82 line_count++;
83 break;
84 case ACTION_GET:
85 if (line > line_count)
86 return 0;
87 last_action_line = i;
88 last_char_index = c;
89 return c;
90 break;
91 case ACTION_REMOVE:
92 if (line > line_count)
93 return 0;
94 len = rb->strlen(&buffer[c])+1;
95 char_count -= len;
96 rb->memmove(&buffer[c],&buffer[c+len],char_count);
97 line_count--;
98 break;
99 case ACTION_UPDATE:
100 if (line > line_count)
101 return 0;
102 len = rb->strlen(&buffer[c])+1;
103 rb->memmove(&buffer[c+rb->strlen(str)+1],&buffer[c+len],char_count);
104 rb->strcpy(&buffer[c],str);
105 char_count += rb->strlen(str)+1-len;
106 break;
107 case ACTION_CONCAT:
108 if (line > line_count)
109 return 0;
110 rb->memmove(&buffer[c-1],&buffer[c],char_count);
111 break;
112 default:
113 return 0;
115 last_action_line = i;
116 last_char_index = c;
117 return 1;
119 char *list_get_name_cb(int selected_item,void* data,char* buf)
121 char *b = &buffer[do_action(ACTION_GET,0,selected_item)];
122 (void)data;
123 if (rb->strlen(b) >= MAX_PATH)
125 char t = b[MAX_PATH-10];
126 b[MAX_PATH-10] = '\0';
127 rb->snprintf(buf,MAX_PATH,"%s ...",b);
128 b[MAX_PATH-10] = t;
130 else rb->strcpy(buf,b);
131 return buf;
133 char filename[MAX_PATH];
134 int get_eol_string(char* fn)
136 int fd=-1;
137 char t;
138 if (!fn)
139 return 0;
140 else if (!fn[0])
141 return 0;
142 fd = rb->PREFIX(open(fn,O_RDONLY));
143 if (fd<0)
144 return 0;
145 eol[0] = '\0';
146 while (!eol[0])
148 if (!rb->read(fd,&t,1))
150 rb->strcpy(eol,"\n");
151 return 0;
153 if (t == '\r')
155 if (rb->read(fd,&t,1) && t=='\n')
156 rb->strcpy(eol,"\r\n");
157 else rb->strcpy(eol,"\r");
159 else if (t == '\n')
161 rb->strcpy(eol,"\n");
164 rb->close(fd);
165 return 1;
168 void save_changes(int overwrite)
170 int fd;
171 int i;
173 if (!filename[0] || !overwrite)
175 rb->strcpy(filename,"/");
176 rb->kbd_input(filename,MAX_PATH);
179 fd = rb->open(filename,O_WRONLY|O_CREAT|O_TRUNC);
180 if (fd < 0)
182 rb->splash(HZ*2, "Changes NOT saved");
183 return;
186 if (!overwrite)
187 /* current directory may have changed */
188 rb->reload_directory();
190 rb->lcd_clear_display();
191 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
192 rb->cpu_boost(1);
193 #endif
194 for (i=0;i<line_count;i++)
196 rb->fdprintf(fd,"%s%s",&buffer[do_action(ACTION_GET,0,i)],eol);
198 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
199 rb->cpu_boost(0);
200 #endif
201 rb->close(fd);
204 void setup_lists(struct gui_synclist *lists, int sel)
206 rb->gui_synclist_init(lists,list_get_name_cb,0, false, 1);
207 rb->gui_synclist_set_icon_callback(lists,NULL);
208 rb->gui_synclist_set_nb_items(lists,line_count);
209 rb->gui_synclist_limit_scroll(lists,true);
210 rb->gui_synclist_select_item(lists, sel);
211 rb->gui_synclist_draw(lists);
213 enum {
214 MENU_RET_SAVE = -1,
215 MENU_RET_NO_UPDATE,
216 MENU_RET_UPDATE,
218 int do_item_menu(int cur_sel, char* copy_buffer)
220 int m, ret = 0;
221 static const struct menu_item items[] = {
222 { "Cut", NULL },
223 { "Copy", NULL },
224 { "", NULL },
225 { "Insert Above", NULL },
226 { "Insert Below", NULL },
227 { "", NULL },
228 { "Cat To Above",NULL },
229 { "", NULL },
230 { "Save", NULL },
232 m = menu_init(rb, items, sizeof(items) / sizeof(*items),
233 NULL, NULL, NULL, NULL);
235 switch (menu_show(m))
237 case 0: /* cut */
238 rb->strcpy(copy_buffer,&buffer[do_action(ACTION_GET,0,cur_sel)]);
239 do_action(ACTION_REMOVE,0,cur_sel);
240 ret = MENU_RET_UPDATE;
241 break;
242 case 1: /* copy */
243 rb->strcpy(copy_buffer,&buffer[do_action(ACTION_GET,0,cur_sel)]);
244 ret = MENU_RET_NO_UPDATE;
245 break;
246 case 2: /* blank */
247 ret = MENU_RET_NO_UPDATE;
248 break;
250 case 3: /* insert above */
251 if (!rb->kbd_input(copy_buffer,MAX_LINE_LEN))
253 do_action(ACTION_INSERT,copy_buffer,cur_sel);
254 copy_buffer[0]='\0';
255 ret = MENU_RET_UPDATE;
257 break;
258 case 4: /* insert below */
259 if (!rb->kbd_input(copy_buffer,MAX_LINE_LEN))
261 do_action(ACTION_INSERT,copy_buffer,cur_sel+1);
262 copy_buffer[0]='\0';
263 ret = MENU_RET_UPDATE;
265 break;
266 case 5: /* blank */
267 ret = MENU_RET_NO_UPDATE;
268 break;
269 case 6: /* cat to above */
270 if (cur_sel>0)
272 do_action(ACTION_CONCAT,0,cur_sel);
273 ret = MENU_RET_UPDATE;
275 break;
276 case 7: /* save */
277 ret = MENU_RET_SAVE;
278 break;
279 default:
280 ret = MENU_RET_NO_UPDATE;
281 break;
283 menu_exit(m);
284 return ret;
287 #ifdef HAVE_LCD_COLOR
288 /* in misc.h but no need to polute the api */
289 #define toupper(c) (((c >= 'a') && (c <= 'z'))?c+'A':c)
290 #define isxdigit(c) ((c>='a' && c<= 'f') || (c>='A' && c<= 'F') \
291 || (c>='0' && c<= '9'))
292 #define hex2dec(c) (((c) >= '0' && ((c) <= '9')) ? (toupper(c)) - '0' : \
293 (toupper(c)) - 'A' + 10)
294 int hex_to_rgb(const char* hex)
295 { int ok = 1;
296 int i;
297 int red, green, blue;
299 if (rb->strlen(hex) == 6) {
300 for (i=0; i < 6; i++ ) {
301 if (!isxdigit(hex[i])) {
302 ok=0;
303 break;
307 if (ok) {
308 red = (hex2dec(hex[0]) << 4) | hex2dec(hex[1]);
309 green = (hex2dec(hex[2]) << 4) | hex2dec(hex[3]);
310 blue = (hex2dec(hex[4]) << 4) | hex2dec(hex[5]);
311 return LCD_RGBPACK(red,green,blue);
315 return 0;
317 #endif /* HAVE_LCD_COLOR */
319 /* this is the plugin entry point */
320 enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
322 int fd;
323 static char temp_line[MAX_LINE_LEN];
325 struct gui_synclist lists;
326 bool exit = false;
327 int button;
328 bool changed = false;
329 int cur_sel=0;
330 static char copy_buffer[MAX_LINE_LEN];
331 bool prev_show_statusbar;
332 #ifdef HAVE_LCD_COLOR
333 bool edit_icons_file = false;
334 #endif
336 rb = api;
338 copy_buffer[0]='\0';
339 prev_show_statusbar = rb->global_settings->statusbar;
340 rb->global_settings->statusbar = false;
342 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
343 rb->cpu_boost(1);
344 #endif
345 if (parameter)
347 #ifdef HAVE_LCD_COLOR
348 char *c = NULL;
349 #endif
350 rb->strcpy(filename,(char*)parameter);
351 if (!get_eol_string(filename))
353 rb->strcpy(eol,"\n");
355 fd = rb->open(filename,O_RDONLY);
356 if (fd<0)
358 rb->splash(HZ*2,"Couldnt open file: %s",(char*)parameter);
359 return PLUGIN_ERROR;
361 #ifdef HAVE_LCD_COLOR
362 c = rb->strchr(filename, '.');
363 if (c && rb->strcmp(c, ".icons"))
364 edit_icons_file = true;
365 #endif
366 /* read in the file */
367 while (rb->read_line(fd,temp_line,MAX_LINE_LEN))
369 if (!do_action(ACTION_INSERT,temp_line,line_count))
371 rb->splash(HZ*2,"Error reading file: %s",(char*)parameter);
372 rb->close(fd);
373 return PLUGIN_ERROR;
376 rb->close(fd);
378 else
380 filename[0] = '\0';
381 rb->strcpy(eol,"\n");
383 #ifdef HAVE_ADJUSTABLE_CPU_FREQ
384 rb->cpu_boost(0);
385 #endif
386 /* now dump it in the list */
387 setup_lists(&lists,0);
388 rb->lcd_update();
389 while (!exit)
391 rb->gui_synclist_draw(&lists);
392 cur_sel = rb->gui_synclist_get_sel_pos(&lists);
393 button = rb->get_action(CONTEXT_LIST,TIMEOUT_BLOCK);
394 if (rb->gui_synclist_do_button(&lists,button,LIST_WRAP_UNLESS_HELD))
395 continue;
396 switch (button)
398 case ACTION_STD_OK:
400 bool edit_text = true;
401 #ifdef HAVE_LCD_COLOR
402 int color;
403 #endif
404 if (line_count)
405 rb->strcpy(temp_line,&buffer[do_action(ACTION_GET,0,cur_sel)]);
406 #ifdef HAVE_LCD_COLOR
407 if (edit_icons_file)
409 char *name = temp_line, *value = NULL;
410 char extension[MAX_LINE_LEN];
411 rb->settings_parseline(temp_line, &name, &value);
412 if (line_count)
414 MENUITEM_STRINGLIST(menu, "Edit What?", NULL,
415 "Extension", "Color",);
416 switch (rb->do_menu(&menu, NULL))
418 case 0:
419 edit_text = true;
420 break;
421 case 1:
422 edit_text = false;
423 if (value)
424 color = hex_to_rgb(value);
425 else color = 0;
426 rb->strcpy(extension, name);
427 rb->set_color(rb->screens[SCREEN_MAIN], name, &color, -1);
428 rb->snprintf(temp_line, MAX_LINE_LEN, "%s: %02X%02X%02X",
429 extension, RGB_UNPACK_RED(color),
430 RGB_UNPACK_GREEN(color),
431 RGB_UNPACK_BLUE(color));
432 if (line_count)
434 do_action(ACTION_UPDATE,temp_line,cur_sel);
436 else do_action(ACTION_INSERT,temp_line,cur_sel);
437 changed = true;
438 break;
442 #endif
443 if (edit_text &&!rb->kbd_input(temp_line,MAX_LINE_LEN))
445 if (line_count)
447 do_action(ACTION_UPDATE,temp_line,cur_sel);
449 else do_action(ACTION_INSERT,temp_line,cur_sel);
450 changed = true;
453 break;
454 #ifdef TEXT_EDITOR_DELETE
455 case TEXT_EDITOR_DELETE:
456 #ifdef TEXT_EDITOR_DELETE_PRE
457 if (last_button != TEXT_EDITOR_DELETE_PRE)
458 break;
459 #endif
460 if (!line_count) break;
461 rb->strcpy(copy_buffer,&buffer[do_action(ACTION_GET,0,cur_sel)]);
462 do_action(ACTION_REMOVE,0,cur_sel);
463 changed = true;
464 break;
465 #endif
466 case ACTION_STD_MENU:
467 { /* do the item menu */
468 switch (do_item_menu(cur_sel, copy_buffer))
470 case MENU_RET_SAVE:
471 save_changes(1);
472 changed = false;
473 break;
474 case MENU_RET_UPDATE:
475 changed = true;
476 break;
477 case MENU_RET_NO_UPDATE:
478 break;
481 break;
482 case ACTION_STD_CANCEL:
483 if (changed)
485 int m;
486 int result;
488 static const struct menu_item items[] = {
489 { "Return", NULL },
490 { " ", NULL },
491 { "Save Changes", NULL },
492 { "Save As...", NULL },
493 { " ", NULL },
494 { "Save and Exit", NULL },
495 { "Ignore Changes and Exit", NULL },
498 m = menu_init(rb, items, sizeof(items) / sizeof(*items),
499 NULL, NULL, NULL, NULL);
501 result=menu_show(m);
503 switch (result)
505 case 0:
506 break;
507 case 2: //save to disk
508 save_changes(1);
509 changed = 0;
510 break;
511 case 3:
512 save_changes(0);
513 changed = 0;
514 break;
516 case 5:
517 save_changes(1);
518 exit=1;
519 break;
520 case 6:
521 exit=1;
522 break;
524 menu_exit(m);
526 else exit=1;
527 break;
529 rb->gui_synclist_set_nb_items(&lists,line_count);
531 rb->global_settings->statusbar = prev_show_statusbar;
532 return PLUGIN_OK;