#define _DEFAULT_SOURCE #include #include #include #include #include #include #include "btk/btk.h" /* for filesystem navigation */ /* change depending on your system */ #define NAME_SIZE 255 #define PATH_SIZE 4096 /* buffer increments */ #define DIR_BUFFER_INC 100 #define DATA_BUFFER_INC 2 #define READ_SIZE 2048 #define TEXT_SIZE 4096 #define ITEM_SIZE 256 #define TAG_COL 0 #define UID_COL 1 #define NAME_COL 2 #define DATA_COLS_N 9 /* excludes id, filename and tag */ enum tag_type { TAG_NONE = 0, TAG_STRAY = 1U << 0, TAG_NEW = 1U << 1, TAG_DEL = 1U << 2, TAG_EDITED = 1U << 3, TAG_VISIBILITY = 1U << 4 }; enum status { STATUS_NONE, STATUS_ERROR, STATUS_LATEST, STATUS_EDITED, STATUS_BUSY }; enum dir_mode { DIR_FILES = 1U << 0, DIR_HIDDEN = 1U << 1 }; typedef struct { char uid[ITEM_SIZE]; char filename[ITEM_SIZE]; char misc[DATA_COLS_N][ITEM_SIZE]; char notes[TEXT_SIZE]; unsigned int notes_w; } data_entry_t; /* window creation functions */ void build_entry_form (); void build_file_picker (); void build_main_window (); void build_settings (); /* functions */ void clean_string_copy (char *, char *, unsigned int, int); int cmp_str_for_qsort (const void *, const void*); void entry_form_apply (); void entry_form_close (); void entry_form_open (); int file_exists (); void file_picker_close (); void file_picker_open (int); void file_picker_open_entry (); void file_picker_open_open (); void file_picker_open_new (); void file_picker_select (); int find_dir_spot (char *, char *); char* get_data_item (unsigned int, unsigned int); void hotpot_close (); void hotpot_load (); void hotpot_reload (); void hotpot_sync (); void hotpot_unload (); void move_dir (int); void open_ext_dir (); void open_ext_entry (int); void quit (); int parse_dir (char *, int); void push_entry (int, data_entry_t); void settings_close (); void settings_open (); void set_activity_status (int, int); void set_entry_tag_col (unsigned int, int); void set_main_win_states (int); void set_file_picker_states (int); void str_to_lower_case (char *); void toggle_del_tag (); void toggle_col (int, btk_arg_t); void toggle_hidden (int, btk_arg_t); void toggle_notes (int, btk_arg_t); void toggle_visibility_tag (); void update_select_data (int); /* btk windows */ btk_session_t *session; btk_window_t *entry_form; btk_window_t *file_picker; btk_window_t *main_win; btk_window_t *settings; btk_window_t *windows[4]; /* btk important cells */ btk_cell_t *table_cell; btk_cell_t *dir_list_cell; btk_cell_t *notes_view_cell; btk_cell_t *file_select_cell; btk_cell_t *activity_cell; btk_cell_t *edited_note_cell; unsigned int table_cell_id; unsigned int dir_list_cell_id; unsigned int notes_view_cell_id; unsigned int file_select_cell_id; unsigned int activity_cell_id; unsigned int edited_note_cell_id; /* file navigation */ char dir_current[PATH_SIZE]; char *dir_list = NULL; int *dir_spot = NULL; unsigned int dir_list_n = 0; int nav_select = -1; int nav_mode; /* hotpot data */ int file_picker_mode; char open_dir[PATH_SIZE] = ""; int hotpot_open = 0; char *data = NULL; char *data_notes = NULL; int *data_tags = NULL; int *data_spot = NULL; char *data_rename = NULL; unsigned int data_n = 0; unsigned int data_buffer_n = 0; unsigned int last_uid = 0; /* selected data */ int select_data = -1; char select_note[TEXT_SIZE]; /* edited data */ char edited_uid[ITEM_SIZE]; char edited_filename[ITEM_SIZE]; char edited_misc[DATA_COLS_N][ITEM_SIZE]; char edited_note[TEXT_SIZE]; /* misc gui stuff */ char status_open_file[PATH_SIZE + NAME_SIZE]; char status_files_n[15]; char status_activity[9]; char misc_titles[DATA_COLS_N][ITEM_SIZE]; char misc_switches[DATA_COLS_N][ITEM_SIZE]; unsigned int data_cols_cw[DATA_COLS_N + 3] = {1, 1, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2}; int data_cols_ena[DATA_COLS_N + 3] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; char data_header[DATA_COLS_N + 3][ITEM_SIZE] = { "TAGS", "UID", "FILENAME", "TOPICS", "AUTHORS", "INSTITUTIONS", "PUBLISHER", "YEAR", "VOL", "NO", "PAGES", "ACCESSED" }; /* misc */ btk_arg_t dummy_arg = {.i = 0, .f = 0.0f, .c = NULL, .p = NULL}; /*******************************************************************/ void build_entry_form() { entry_form = btk_window_create(session->x_con, session->x_scr, session->x_vis, 8, 6 + DATA_COLS_N, 4, 3 + DATA_COLS_N, 100, 0, 10 + 2 * DATA_COLS_N, entry_form_close); btk_window_set_name(entry_form, "hotpot - entry form"); edited_note_cell_id = 0; edited_note_cell = &(entry_form->cells[edited_note_cell_id]); btk_cell_set_editor(edited_note_cell, 2, 2 + DATA_COLS_N, 6, 4, (char*)&edited_note, TEXT_SIZE); btk_cell_set_mark (&(entry_form->cells[1]), 0, 0, 2, BTK_JUSTIFY_RIGHT, "uid :"); btk_cell_set_mark (&(entry_form->cells[2]), 0, 1, 2, BTK_JUSTIFY_RIGHT, "filename :"); btk_cell_set_mark (&(entry_form->cells[3]), 2, 0, 6, BTK_JUSTIFY_LEFT, edited_uid); btk_cell_set_input (&(entry_form->cells[4]), 2, 1, 6, (char*)edited_filename, NAME_SIZE); btk_cell_set_mark (&(entry_form->cells[5]), 0, 2 + DATA_COLS_N, 2, BTK_JUSTIFY_RIGHT, "notes :"); btk_cell_set_empty (&(entry_form->cells[6]), 0, 3 + DATA_COLS_N, 2, 1); btk_cell_set_button (&(entry_form->cells[7]), 0, 4 + DATA_COLS_N, 2, "apply", entry_form_apply); btk_cell_set_button (&(entry_form->cells[8]), 0, 5 + DATA_COLS_N, 2, "close", entry_form_close); /* generate input fields for misc data */ for (int i = 0; i < DATA_COLS_N; i++) { strcpy(misc_titles[i], data_header[i + 3]); str_to_lower_case(misc_titles[i]); strcat(misc_titles[i], " :"); btk_cell_set_mark (&(entry_form->cells[9 + i]), 0, 2 + i, 2, BTK_JUSTIFY_RIGHT, misc_titles[i]); btk_cell_set_input (&(entry_form->cells[10 + DATA_COLS_N + i]), 2, 2 + i, 6, (char*)edited_misc[i], NAME_SIZE); } } void build_file_picker() { file_picker = btk_window_create(session->x_con, session->x_scr, session->x_vis, 4, 9, 2, 2, 0, 300, 5, file_picker_close); btk_window_set_name(file_picker, "hotpot - file picker"); dir_list_cell_id = 0; dir_list_cell = &(file_picker->cells[dir_list_cell_id]); btk_cell_set_list (dir_list_cell, 0, 1, 4, 5, &dir_list, &dir_list_n, NAME_SIZE, NULL, NULL, &dir_spot, move_dir, set_file_picker_states); file_select_cell_id = 1; file_select_cell = &(file_picker->cells[file_select_cell_id]); btk_cell_set_button (file_select_cell, 0, 7, 4, "select", file_picker_select); btk_cell_set_mark (&(file_picker->cells[2]), 0, 0, 4, BTK_JUSTIFY_RIGHT, (char*)&dir_current); btk_cell_set_switch (&(file_picker->cells[3]), 0, 6, 4, "show hidden", toggle_hidden, dummy_arg); btk_cell_set_button (&(file_picker->cells[4]), 0, 8, 4, "cancel", file_picker_close); } void build_main_window() { main_win = btk_window_create(session->x_con, session->x_scr, session->x_vis, 13, 16, 4, 7, 400, 0, 21, quit); btk_window_set_name(main_win, "hotpot"); table_cell_id = 0; table_cell = &(main_win->cells[table_cell_id]); btk_cell_set_table(table_cell, 3, 0, 10, 15, (char*)data_header, &data, ITEM_SIZE, DATA_COLS_N + 3, &data_n, (int*)data_cols_ena, data_cols_cw, 2, NULL, NULL, &data_spot, open_ext_entry, update_select_data); strcpy(select_note, ""); notes_view_cell_id = 1; notes_view_cell = &(main_win->cells[notes_view_cell_id]); btk_cell_set_editor(notes_view_cell, 3, 9, 10, 6, select_note, TEXT_SIZE); main_win->cells[notes_view_cell_id].state |= BTK_CELL_STATE_DISABLED; main_win->cells[notes_view_cell_id].state |= BTK_CELL_STATE_HIDDEN; activity_cell_id = 18; activity_cell = &(main_win->cells[activity_cell_id]); btk_cell_set_mark (activity_cell, 11, 15, 2, BTK_JUSTIFY_RIGHT, (char*)&status_activity); btk_cell_set_button (&(main_win->cells[2]), 0, 0, 3, "open", file_picker_open_open); btk_cell_set_button (&(main_win->cells[3]), 0, 1, 3, "new", file_picker_open_new); btk_cell_set_button (&(main_win->cells[4]), 0, 2, 3, "sync", hotpot_sync); btk_cell_set_button (&(main_win->cells[5]), 0, 3, 3, "view directory", open_ext_dir); btk_cell_set_button (&(main_win->cells[6]), 0, 4, 3, "reload", hotpot_reload); btk_cell_set_button (&(main_win->cells[7]), 0, 5, 3, "close", hotpot_close); btk_cell_set_button (&(main_win->cells[8]), 0, 6, 3, "quit", quit); btk_cell_set_empty (&(main_win->cells[9]), 0, 7, 3, 1); btk_cell_set_button (&(main_win->cells[10]), 0, 8, 3, "add file", file_picker_open_entry); btk_cell_set_button (&(main_win->cells[11]), 0, 9, 3, "edit entry", entry_form_open); btk_cell_set_button (&(main_win->cells[12]), 0, 10, 3, "tag for deletion", toggle_del_tag); btk_cell_set_button (&(main_win->cells[19]), 0, 11, 3, "tag for visibility", toggle_visibility_tag); btk_cell_set_button (&(main_win->cells[20]), 0, 12, 3, "export citation", NULL); btk_cell_set_empty (&(main_win->cells[13]), 0, 13, 3, 1); btk_cell_set_switch (&(main_win->cells[14]), 0, 14, 3, "show notes", toggle_notes, dummy_arg); btk_cell_set_button (&(main_win->cells[15]), 0, 15, 3, "settings", settings_open); btk_cell_set_mark (&(main_win->cells[16]), 3, 15, 6, BTK_JUSTIFY_RIGHT, (char*)&status_open_file); btk_cell_set_mark (&(main_win->cells[17]), 9, 15, 2, BTK_JUSTIFY_RIGHT, (char*)&status_files_n); main_win->cells[15].state |= BTK_CELL_STATE_ON; main_win->cells[20].state |= BTK_CELL_STATE_DISABLED; set_main_win_states(0); } void build_settings() { settings = btk_window_create(session->x_con, session->x_scr, session->x_vis, 3, 3 + DATA_COLS_N, -1, -1, 0, 0, 3 + DATA_COLS_N, settings_close); btk_window_set_name(settings, "hotpot - settings"); btk_arg_t col_arg= {.i = TAG_COL}; btk_cell_set_switch (&(settings->cells[0]), 0, 0, 3, "show tags", toggle_col, col_arg); btk_cell_set_empty (&(settings->cells[1 + DATA_COLS_N]), 0, 1 + DATA_COLS_N, 3, 1); btk_cell_set_button (&(settings->cells[2 + DATA_COLS_N]), 0, 2 + DATA_COLS_N, 3, "close", settings_close); settings->cells[0].state |= BTK_CELL_STATE_ON; /* generate col toggles for misc data */ for (int i = 0; i < DATA_COLS_N; i++) { sprintf(misc_switches[i], "show %s", data_header[i + 3]); str_to_lower_case(misc_switches[i]); btk_arg_t col_arg = {.i = i + 3}; btk_cell_set_switch (&(settings->cells[1 + i]), 0, 1 + i, 3, misc_switches[i], toggle_col, col_arg); settings->cells[1 + i].state |= BTK_CELL_STATE_ON; } } /****************************************************************************************/ int main() { getcwd(dir_current, PATH_SIZE); strcat(dir_current, "/"); /* btk init */ session = btk_open(); build_entry_form(); build_file_picker(); build_main_window(); build_settings(); windows[0] = main_win; windows[1] = file_picker; windows[2] = entry_form; windows[3] = settings; btk_map(session, main_win); btk_loop(session, windows, 4); btk_close(session); quit(); return 0; } /****************************************************************************************/ void clean_string_copy(char* dest, char* src, unsigned int max_size, int del_newline) { if (!max_size) return; char mid[max_size]; strncpy(mid, src, max_size); if (del_newline) *strchr(mid, '\n') = '\0'; mid[max_size - 1] = '\0'; strcpy(dest, mid); } int cmp_str_for_qsort(const void *a, const void *b) { return strcmp((char*)a, (char*)b); } /* check if any data changed, copy it and update status if so */ void entry_form_apply() { int changed = 0; if (strcmp(edited_filename, get_data_item(NAME_COL, select_data))) { strcpy(data_rename + select_data * ITEM_SIZE, get_data_item(NAME_COL, select_data)); strcpy(get_data_item(NAME_COL, select_data), edited_filename); changed = 1; } for (int i = 0; i < DATA_COLS_N; i++) { if (strcmp(edited_misc[i], get_data_item(i + 3, select_data))) { strcpy(get_data_item(i + 3, select_data), edited_misc[i]); changed = 1; } } if (strcmp(edited_note, select_note)) { strcpy(data_notes + select_data * TEXT_SIZE, edited_note); strcpy(select_note, edited_note); btk_cell_editor_update_text(notes_view_cell); changed = 1; } if (changed) { data_tags[select_data] |= TAG_EDITED; set_entry_tag_col(select_data, data_tags[select_data]); set_activity_status(STATUS_EDITED, 1); btk_window_redraw(main_win); } } void entry_form_open() { if (select_data < 0 || select_data >= data_n) return; strcpy(edited_note, select_note); btk_cell_editor_reset_caret(edited_note_cell); strcpy(edited_uid, get_data_item(1, select_data)); strcpy(edited_filename, get_data_item(2, select_data)); btk_cell_input_reset_caret(&(entry_form->cells[4])); for (int i = 0; i < DATA_COLS_N; i++) { strcpy(edited_misc[i], get_data_item(i + 3, select_data)); btk_cell_input_reset_caret(&(entry_form->cells[10 + DATA_COLS_N + i])); } btk_cell_editor_update_text(edited_note_cell); btk_window_disable(main_win); btk_map(session, entry_form); } void entry_form_close() { btk_window_enable(main_win); btk_unmap(session, entry_form); } /* checks if currently selected file in the file picker * exists in the open hotpot directory */ int file_exists() { DIR *d; struct dirent *dir; d = opendir(open_dir); if (!d) { printf("ERROR - couldn't read directory"); return 0; } while ((dir = readdir(d))) { if (!strcmp(dir->d_name, dir_list + nav_select * NAME_SIZE)) return 1; } return 0; } void file_picker_close() { btk_window_enable(main_win); btk_unmap(session, file_picker); } void file_picker_open(int mode) { btk_cell_list_deselect(dir_list_cell); set_file_picker_states(-1); file_picker_mode = mode; if (!file_picker_mode) { nav_mode |= DIR_FILES; } else { nav_mode &= ~DIR_FILES; } btk_window_disable(main_win); btk_map(session, file_picker); parse_dir(dir_current, nav_mode); } void file_picker_open_entry() { file_picker_open(0); } void file_picker_open_open() { file_picker_open(1); } void file_picker_open_new() { file_picker_open(2); } void file_picker_select() { if (nav_select < 0 || nav_select >= dir_list_n) { printf("ERROR -> tried to open out of bound selection\n"); return; } switch(file_picker_mode) { /* copy selected file into open hotpot directory, then add it to data */ case 0:; /* compose command */ char cmd[12 + 2 * PATH_SIZE + NAME_SIZE]; sprintf(cmd, "cp -n \'%s%s\' \'%s\'", dir_current, dir_list + nav_select * NAME_SIZE, open_dir); system(cmd); /* create new entry */ data_entry_t new_entry; new_entry.notes_w = 0; new_entry.notes[0] = '\0'; new_entry.uid[0] = '\0'; for (int i = 0; i < DATA_COLS_N; i++) new_entry.misc[i][0] = '\0'; strcpy(new_entry.filename, dir_list + nav_select * NAME_SIZE); push_entry(TAG_NEW, new_entry); /* update window */ set_activity_status(STATUS_EDITED, 0); set_entry_tag_col(data_n - 1, data_tags[data_n - 1]); set_main_win_states(1); break; /* open hotpot */ case 1: strcpy(open_dir, dir_current); strcat(open_dir, dir_list + nav_select * NAME_SIZE); hotpot_load(); break; /* create hotpot file, then open it */ case 2: strcpy(open_dir, dir_current); strcat(open_dir, dir_list + nav_select * NAME_SIZE); char hotpot_file[PATH_SIZE + NAME_SIZE]; strcpy(hotpot_file, open_dir); strcat(hotpot_file, "/.htpt"); FILE *f = fopen(hotpot_file, "w"); if (!f) { set_activity_status(STATUS_ERROR, 1); printf("ERROR - couldn't create new hotpot file"); return; } fclose(f); hotpot_load(); break; } file_picker_close(); set_main_win_states(1); } /* returns the hilight color index of the directory */ int find_dir_spot(char *search_dir, char *sub_dir) { char search_path[PATH_SIZE]; strcpy(search_path, search_dir); strcat(search_path, sub_dir); strcat(search_path, "/"); DIR *d; struct dirent *dir; struct stat file_stats; char file_path[PATH_SIZE + NAME_SIZE]; d = opendir(search_path); if (!d) { printf("ERROR - couldn't read directory"); return 0; } while ((dir = readdir(d))) { strcpy(file_path, search_path); strcat(file_path, dir->d_name); stat(file_path, &file_stats); if ((file_stats.st_mode & S_IFMT) != S_IFDIR) { if (strcmp(dir->d_name, ".htpt") == 0) { return 1; } } } return 2; } char* get_data_item(unsigned int col, unsigned int row) { return data + ITEM_SIZE * (col + row * (DATA_COLS_N + 3)); } void hotpot_close() { hotpot_unload(); select_data = -1; strcpy(select_note, ""); strcpy(open_dir, ""); set_main_win_states(1); } void hotpot_load() { set_activity_status(STATUS_BUSY, 1); char hotpot_file[PATH_SIZE + NAME_SIZE]; strcpy(hotpot_file, open_dir); strcat(hotpot_file, "/.htpt"); FILE *f = fopen(hotpot_file, "r"); if (!f) { printf("ERROR - couldn't read hotpot file\n"); set_activity_status(STATUS_ERROR, 1); return; } hotpot_unload(); /* arrays preparation for on the go resizing */ data_buffer_n = DATA_BUFFER_INC; data = malloc(data_buffer_n * ITEM_SIZE * (DATA_COLS_N + 3)); data_notes = malloc(data_buffer_n * TEXT_SIZE); data_tags = malloc(data_buffer_n * sizeof(int)); data_spot = malloc(data_buffer_n * sizeof(int)); data_rename = malloc(data_buffer_n * ITEM_SIZE); /* used for syncing later */ data_entry_t entry_read; char line_read[READ_SIZE]; char *last_newline; int in_section = 0, col = 0, w = 0; /* get lastest used uid on the first line */ /* if it is not present it will be regenerated after the rest of the file has * been parsed */ if (fgets(line_read, READ_SIZE, f) && line_read[0] == '-') { last_uid = strtol(line_read + 1, NULL, 16); } else { last_uid = 0; /* to be regenerated */ } /* parse rest of .htpt file */ while (fgets(line_read, READ_SIZE, f)) { switch (line_read[0]) { /* open section, reset entry_read */ case '[': if (in_section) break; in_section = 1; col = 0; w = 0; entry_read.notes_w = 0; entry_read.notes[0] = '\0'; entry_read.uid[0] = '\0'; entry_read.filename[0] = '\0'; for (int i = 0; i < DATA_COLS_N; i++) entry_read.misc[i][0] = '\0'; break; /* close section, push entry_read to data */ case ']': if (!in_section) break; in_section = 0; last_newline = strrchr(entry_read.notes, '\n'); if (last_newline) /* remove last unecessary newline */ *last_newline = '\0'; push_entry(TAG_STRAY, entry_read); break; /* get uid */ case '-': if (!in_section) break; clean_string_copy(entry_read.uid, line_read + 1, ITEM_SIZE, 1); break; /* get filename */ case '+': if (!in_section) break; clean_string_copy(entry_read.filename, line_read + 1, ITEM_SIZE, 1); break; /* get other data */ case '.': if (!in_section || col >= DATA_COLS_N) break; clean_string_copy(entry_read.misc[col], line_read + 1, ITEM_SIZE, 1); col++; break; /* get notes on multiple lines until section close or reaching TEXT_SIZE * character limit */ case '*': if (!in_section) break; w = strlen(line_read + 1); if (entry_read.notes_w + w >= TEXT_SIZE) { w = TEXT_SIZE - entry_read.notes_w; } else { strncat(entry_read.notes, line_read + 1, w); } entry_read.notes_w += w; break; default: break; } } fclose(f); /* regenerate uid if needed */ if (!last_uid) { for (int i = 0; i < data_n; i++) if (last_uid < strtol(get_data_item(UID_COL, i), NULL, 16)) last_uid = strtol(get_data_item(UID_COL, i), NULL, 16); } /* parse all files in hotpot directory and match them to parsed data */ DIR *d = opendir(open_dir); struct dirent *dir; struct stat file_stats; char file_path[PATH_SIZE + NAME_SIZE]; int found = 0; int new_files = 0; int n = data_n; int is_dir; data_entry_t dummy_entry; dummy_entry.uid[0] = '\0'; dummy_entry.notes[0] = '\0'; dummy_entry.notes_w = 0; for (int i = 0; i < DATA_COLS_N; i++) dummy_entry.misc[i][0] = '\0'; /* scan found files */ while ((dir = readdir(d))) { found = 0; strcpy(file_path, open_dir); strcat(file_path, dir->d_name); stat(file_path, &file_stats); is_dir = (file_stats.st_mode & S_IFMT) == S_IFDIR ? 1 : 0; //printf("%i\t%s\n", is_dir, dir->d_name); TODO fix directory detection if (is_dir || dir->d_name[0] == '.') { continue; } for (int i = 0; i < n; i++) { if (strcmp(dir->d_name, get_data_item(NAME_COL, i)) == 0) { data_tags[i] = TAG_NONE; found = 1; break; } } /* if file not found, create new entry */ if (!found) { clean_string_copy(dummy_entry.filename, dir->d_name, ITEM_SIZE, 0); push_entry(TAG_NEW, dummy_entry); new_files = 1; } } /* set tag values in data */ for (int i = 0; i < data_n; i++) { set_entry_tag_col(i, data_tags[i]); } btk_cell_table_deselect(table_cell); strcpy(select_note, ""); set_activity_status((new_files ? STATUS_EDITED : STATUS_LATEST), 1); return; } void hotpot_reload() { hotpot_unload(); select_data = -1; strcpy(select_note, ""); hotpot_load(); set_main_win_states(1); } void hotpot_sync() { set_activity_status(STATUS_BUSY, 1); char hotpot_file[PATH_SIZE + NAME_SIZE]; strcpy(hotpot_file, open_dir); strcat(hotpot_file, "/.htpt"); FILE *f = fopen(hotpot_file, "w"); if (!f) { printf("ERROR - couldn't write hotpot file\n"); set_activity_status(STATUS_ERROR, 1); return; } /* output last uid */ fprintf(f, "-%X\n\n", last_uid); /* output rows of data */ char *note_token; char note[TEXT_SIZE]; for (int i = 0; i < data_n; i++) { if ((data_tags[i] & TAG_DEL)) continue; fprintf(f, "[\n"); fprintf(f, "-%s\n", get_data_item(UID_COL, i)); fprintf(f, "+%s\n", get_data_item(NAME_COL, i)); for (int j = 0; j < DATA_COLS_N; j++) fprintf(f, ".%s\n", get_data_item(j + 3, i)); // TODO /* strcpy(note, data_notes + i * TEXT_SIZE); char *lim = strchr(note, '\n'); while (lim) { *lim = '\0'; printf("%lu >> \'%s\'\n", strlen(note), note); fprintf(f, "*%s\n", note); strcpy(note, ++lim); lim = strchr(note, '\n'); } */ strcpy(note, data_notes + i * TEXT_SIZE); note_token = strtok(note, "\n"); while (note_token) { fprintf(f, "*%s\n", note_token); note_token = strtok(NULL, "\n"); } fprintf(f, "]\n\n"); } fclose(f); /* rename edited files if those are not stray entries */ char cmd_mv[11 + 2 * (PATH_SIZE + NAME_SIZE)]; for (int i = 0; i < data_n; i++) { if (!(data_tags[i] & TAG_STRAY) && strcmp(data_rename + i * ITEM_SIZE, "")) { sprintf(cmd_mv, "mv \'%s/%s\' \'%s/%s\'", open_dir, data_rename + i * ITEM_SIZE, open_dir, get_data_item(NAME_COL, i)); system(cmd_mv); } } /* delete rows with DEL tag by shifting down */ int c = 0; char mid[TEXT_SIZE]; /* just to get rid of pointer overlap warning */ char cmd_rm[5 + PATH_SIZE + NAME_SIZE]; for (int i = 0; i < data_n; i++) { if (c) { data_tags[i - c] = data_tags[i]; strcpy(mid, data_notes + i * TEXT_SIZE); strcpy(data_notes + (i - c) * TEXT_SIZE, mid); for (int j = 0; j < DATA_COLS_N + 3; j++) strcpy(get_data_item(j, i - c), get_data_item(j, i)); } if ((data_tags[i] & TAG_DEL)) { if (!(data_tags[i] & TAG_STRAY)) { sprintf(cmd_rm, "rm \'%s/%s\'", open_dir, get_data_item(NAME_COL, i)); system(cmd_rm); } c++; } } data_n -= c; /* remove EDIT and NEW tags */ for (int i = 0; i < data_n; i++) { data_tags[i] &= ~(TAG_EDITED | TAG_NEW); } /* update tag values in data */ for (int i = 0; i < data_n; i++) { set_entry_tag_col(i, data_tags[i]); } set_activity_status(STATUS_LATEST, 0); btk_window_redraw(main_win); return; } void hotpot_unload() { if (data_n) { free(data); free(data_tags); free(data_spot); free(data_notes); free(data_rename); data_n = 0; } } void move_dir(int dir_sel) { if (dir_sel < 0 || !dir_spot[dir_sel]) return; /* if first item selected ("..") go one dir up, unless at / */ if (!dir_sel) { if (strlen(dir_current) <= 1) return; dir_current[strlen(dir_current) - 1] = '\0'; *(strrchr(dir_current, '/')) = '\0'; } else { char dir_sel_name[PATH_SIZE]; strcpy(dir_sel_name, dir_list + dir_sel * NAME_SIZE); strcat(dir_current, dir_sel_name); } strcat(dir_current, "/"); parse_dir(dir_current, nav_mode); btk_window_redraw_cell(file_picker, dir_list_cell_id); btk_window_redraw_cell(file_picker, 2); if (dir_sel) { btk_cell_list_deselect(dir_list_cell); set_file_picker_states(-1); } } void open_ext_dir() { char *args[PATH_SIZE]={"./EXEC", open_dir, NULL}; int pid = fork(); if (pid == 0) { execvp("xdg-open", args); } else if (pid < 0) { printf("ERROR - couldn't open hotpot directory in file explorer\n"); } } void open_ext_entry(int sel) { if (sel < 0) return; char file[PATH_SIZE + ITEM_SIZE]; strcpy(file, open_dir); strcat(file, "/"); strcat(file, get_data_item(NAME_COL, sel)); char *args[PATH_SIZE]={"./EXEC", file, NULL}; int pid = fork(); if (pid == 0) { execvp("xdg-open", args); } else if (pid < 0) { printf("ERROR - couldn't open item externaly\n"); } } int parse_dir(char *dir_path, int mode) { DIR *d = opendir(dir_path); if (!d) { printf("ERROR - couldn't read directory"); return 1; } struct dirent *dir; struct stat file_stats; char file_path[PATH_SIZE + NAME_SIZE]; unsigned int dir_buffer_n = DIR_BUFFER_INC; int c = 0; int is_dir; if (dir_list_n) { free(dir_list); free(dir_spot); } dir_list_n = 0; dir_list = malloc(dir_buffer_n * NAME_SIZE); dir_spot = malloc(dir_buffer_n * sizeof(int)); while ((dir = readdir(d))) { sprintf(file_path, "%s%s", dir_path, dir->d_name); stat(file_path, &file_stats); is_dir = (file_stats.st_mode & S_IFMT) == S_IFDIR ? 1 : 0; if (((mode & DIR_FILES) || is_dir) && c && ((mode & DIR_HIDDEN) || dir->d_name[0] != '.' || c < 2)) { strcpy(dir_list + dir_list_n * NAME_SIZE, dir->d_name); if (is_dir) /* adding '/' to dir items to easily detect them when sorting */ strcat(dir_list + dir_list_n * NAME_SIZE, "/"); dir_spot[dir_list_n] = 0; dir_list_n++; } c++; if (dir_list_n >= dir_buffer_n - 1) { dir_buffer_n += DIR_BUFFER_INC; dir_spot = realloc(dir_spot, dir_buffer_n * sizeof(int)); dir_list = realloc(dir_list, dir_buffer_n * NAME_SIZE); } } /* sort dir list and set hilights */ char *a; qsort(dir_list, dir_list_n, NAME_SIZE, cmp_str_for_qsort); for (int i = 0; i < dir_list_n; i++) { a = strchr(dir_list + i * NAME_SIZE, '/'); if (a) { *a = '\0'; dir_spot[i] = find_dir_spot(dir_path, dir_list + i * NAME_SIZE); } } return 0; } void push_entry(int tag, data_entry_t entry) { if (!data) { printf("ERROR - tried to push entry into NULL data array\n"); return; } if (data_n >= data_buffer_n - 1) { data_buffer_n += DATA_BUFFER_INC; data = realloc(data, data_buffer_n * ITEM_SIZE * (DATA_COLS_N + 3)); data_notes = realloc(data_notes, data_buffer_n * TEXT_SIZE); data_tags = realloc(data_tags, data_buffer_n * sizeof(int)); data_spot = realloc(data_spot, data_buffer_n * sizeof(int)); data_rename = realloc(data_rename, data_buffer_n * ITEM_SIZE); } if (!strcmp(entry.uid, "")) { char new_uid[64]; sprintf(new_uid, "%X", ++last_uid); strcpy(get_data_item(UID_COL, data_n), new_uid); } else { strcpy(get_data_item(UID_COL, data_n), entry.uid); } data_tags[data_n] = tag; strcpy(data_rename + data_n * ITEM_SIZE, ""); strcpy(get_data_item(NAME_COL, data_n), entry.filename); for (int i = 0; i < DATA_COLS_N; i++) strcpy(get_data_item(i + 3, data_n), entry.misc[i]); strcpy(data_notes + data_n * TEXT_SIZE, entry.notes); data_n++; } void quit() { hotpot_unload(); if (dir_list_n) { free(dir_list); free(dir_spot); } btk_window_destroy(entry_form); btk_window_destroy(file_picker); btk_window_destroy(main_win); btk_window_destroy(settings); btk_close(session); exit(0); } void settings_close() { btk_window_enable(main_win); btk_unmap(session, settings); } void settings_open() { btk_window_disable(main_win); btk_map(session, settings); } void set_activity_status(int status, int redraw) { switch (status) { case STATUS_NONE: strcpy(status_activity, "n/a"); activity_cell->state |= BTK_CELL_STATE_DISABLED; activity_cell->state &= ~BTK_CELL_STATE_BELL; activity_cell->state &= ~BTK_CELL_STATE_ON; break; case STATUS_ERROR: strcpy(status_activity, "error"); activity_cell->state &= ~BTK_CELL_STATE_DISABLED; activity_cell->state |= BTK_CELL_STATE_BELL; activity_cell->state &= ~BTK_CELL_STATE_ON; break; case STATUS_LATEST: strcpy(status_activity, "synced"); activity_cell->state &= ~BTK_CELL_STATE_DISABLED; activity_cell->state &= ~BTK_CELL_STATE_BELL; activity_cell->state &= ~BTK_CELL_STATE_ON; break; case STATUS_EDITED: strcpy(status_activity, "unsynced"); activity_cell->state &= ~BTK_CELL_STATE_DISABLED; activity_cell->state &= ~BTK_CELL_STATE_BELL; activity_cell->state |= BTK_CELL_STATE_ON; break; case STATUS_BUSY: strcpy(status_activity, "busy"); activity_cell->state &= ~BTK_CELL_STATE_DISABLED; activity_cell->state |= BTK_CELL_STATE_BELL; activity_cell->state &= ~BTK_CELL_STATE_ON; break; } if (redraw) btk_window_redraw_cell(main_win, activity_cell_id); } void set_edit_tag() { if (select_data < 0 || select_data >= data_n) { printf("ERROR - can't tag out of bounds selection\n"); return; } data_tags[select_data] |= TAG_DEL; btk_window_redraw_cell(main_win, table_cell_id); } void set_entry_tag_col(unsigned int id, int tag) { if (tag & TAG_DEL) { strcpy(get_data_item(TAG_COL, id), "DEL"); data_spot[id] = 1; } else if (tag & TAG_VISIBILITY) { strcpy(get_data_item(TAG_COL, id), "FOCUS"); data_spot[id] = 4; } else if (tag & TAG_EDITED){ strcpy(get_data_item(TAG_COL, id), "EDIT"); data_spot[id] = 1; } else if (tag & TAG_STRAY){ strcpy(get_data_item(TAG_COL, id), "STRAY"); data_spot[id] = 3; } else if (tag & TAG_NEW){ strcpy(get_data_item(TAG_COL, id), "NEW"); data_spot[id] = 2; } else { strcpy(get_data_item(TAG_COL, id), ""); data_spot[id] = 0; } } void set_file_picker_states(int sel) { nav_select = sel; if (sel < 1) { /* separate to avoid segfault */ file_select_cell->state |= BTK_CELL_STATE_DISABLED; btk_window_redraw_cell(file_picker, file_select_cell_id); return; } switch (file_picker_mode) { case 0:; char forbidden_path[PATH_SIZE]; strcpy(forbidden_path, open_dir); strcat(forbidden_path, "/"); if(!dir_spot[sel] && strcmp(dir_current, forbidden_path) != 0 && !file_exists()) { file_select_cell->state &= ~(BTK_CELL_STATE_DISABLED); } else { file_select_cell->state |= BTK_CELL_STATE_DISABLED; } break; case 1: if (dir_spot[sel] == 1) { file_select_cell->state &= ~(BTK_CELL_STATE_DISABLED); } else { file_select_cell->state |= BTK_CELL_STATE_DISABLED; } break; case 2: if (dir_spot[sel] == 1) { file_select_cell->state |= BTK_CELL_STATE_DISABLED; } else { file_select_cell->state &= ~(BTK_CELL_STATE_DISABLED); } break; } btk_window_redraw_cell(file_picker, file_select_cell_id); } void set_main_win_states(int redraw) { if (!(strcmp(open_dir, ""))) { main_win->cells[4].state |= BTK_CELL_STATE_DISABLED; main_win->cells[5].state |= BTK_CELL_STATE_DISABLED; main_win->cells[6].state |= BTK_CELL_STATE_DISABLED; main_win->cells[7].state |= BTK_CELL_STATE_DISABLED; main_win->cells[10].state |= BTK_CELL_STATE_DISABLED; main_win->cells[16].state |= BTK_CELL_STATE_DISABLED; main_win->cells[17].state |= BTK_CELL_STATE_DISABLED; strcpy(status_open_file, "no hotpot opened"); strcpy(status_files_n, "n/a entries"); set_activity_status(STATUS_NONE, redraw); } else { main_win->cells[4].state &= ~BTK_CELL_STATE_DISABLED; main_win->cells[5].state &= ~BTK_CELL_STATE_DISABLED; main_win->cells[6].state &= ~BTK_CELL_STATE_DISABLED; main_win->cells[7].state &= ~BTK_CELL_STATE_DISABLED; main_win->cells[10].state &= ~BTK_CELL_STATE_DISABLED; main_win->cells[16].state &= ~BTK_CELL_STATE_DISABLED; main_win->cells[17].state &= ~BTK_CELL_STATE_DISABLED; strcpy(status_open_file, open_dir); sprintf(status_files_n, "%i entries", data_n); } if (data_n < 1 || select_data < 0) { main_win->cells[11].state |= BTK_CELL_STATE_DISABLED; main_win->cells[12].state |= BTK_CELL_STATE_DISABLED; main_win->cells[19].state |= BTK_CELL_STATE_DISABLED; } else { main_win->cells[11].state &= ~BTK_CELL_STATE_DISABLED; main_win->cells[12].state &= ~BTK_CELL_STATE_DISABLED; main_win->cells[19].state &= ~BTK_CELL_STATE_DISABLED; } if (redraw) btk_window_redraw(main_win); } void str_to_lower_case(char *str) { for (int i = 0; str[i] != '\0'; i++) if(str[i] >= 'A' && str[i] <= 'Z') str[i] += 32; } void toggle_col(int val, btk_arg_t arg) { data_cols_ena[arg.i] = val; btk_cell_table_update_geometry(table_cell); btk_window_redraw_cell(main_win, table_cell_id); } void toggle_del_tag() { if (select_data < 0 || select_data >= data_n) { printf("ERROR - can't tag out of bounds selection\n"); return; } data_tags[select_data] ^= TAG_DEL; set_entry_tag_col(select_data, data_tags[select_data]); set_activity_status(STATUS_EDITED, 1); btk_window_redraw_cell(main_win, table_cell_id); } void toggle_hidden(int i, btk_arg_t arg) { if (i) { nav_mode |= DIR_HIDDEN; } else { nav_mode &= ~DIR_HIDDEN; } parse_dir(dir_current, nav_mode); btk_window_redraw_cell(file_picker, dir_list_cell_id); } void toggle_notes(int val, btk_arg_t arg) { if (val) { table_cell->ca.h = 9; notes_view_cell->state &= ~BTK_CELL_STATE_HIDDEN; } else { table_cell->ca.h = 15; notes_view_cell->state |= BTK_CELL_STATE_HIDDEN; } btk_window_update_cell_area(main_win, table_cell); btk_window_redraw(main_win); } void toggle_visibility_tag() { if (select_data < 0 || select_data >= data_n) { printf("ERROR - can't tag out of bounds selection\n"); return; } data_tags[select_data] ^= TAG_VISIBILITY; set_entry_tag_col(select_data, data_tags[select_data]); btk_window_redraw_cell(main_win, table_cell_id); } void update_select_data(int sel) { select_data = sel; if (sel > data_n) select_data = -1; if (select_data < 0) { strcpy(select_note, ""); main_win->cells[11].state |= BTK_CELL_STATE_DISABLED; main_win->cells[12].state |= BTK_CELL_STATE_DISABLED; main_win->cells[19].state |= BTK_CELL_STATE_DISABLED; } else { strcpy(select_note,data_notes + sel * TEXT_SIZE); main_win->cells[11].state &= ~BTK_CELL_STATE_DISABLED; main_win->cells[12].state &= ~BTK_CELL_STATE_DISABLED; main_win->cells[19].state &= ~BTK_CELL_STATE_DISABLED; } btk_cell_editor_update_text(notes_view_cell); btk_window_redraw_cell(main_win, notes_view_cell_id); btk_window_redraw_cell(main_win, 11); btk_window_redraw_cell(main_win, 12); btk_window_redraw_cell(main_win, 19); }