/* * Copyright (c) 2022 joshua stein * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include "amend.h" #include "browser.h" #include "editor.h" #include "focusable.h" #include "repo.h" #include "tetab.h" #include "util.h" #define LABEL_FONT geneva #define LABEL_FONT_SIZE 10 #define PADDING 10 bool editor_close(struct focusable *focusable); void editor_idle(struct focusable *focusable, EventRecord *event); void editor_update(struct focusable *focusable, EventRecord *event); void editor_suspend(struct focusable *focusable); void editor_resume(struct focusable *focusable); void editor_key_down(struct focusable *focusable, EventRecord *event); void editor_mouse_down(struct focusable *focusable, EventRecord *event); bool editor_handle_menu(struct focusable *focusable, short menu, short item); void editor_update_menu(struct editor *editor); void editor_save(struct editor *editor); void editor_init(struct browser *browser, struct repo_amendment *amendment) { Str255 title, filename; struct editor *editor; struct focusable *focusable; char date[32]; Rect bounds = { 0 }, te_bounds = { 0 }; TextStyle style; short fh, off; struct tm *ttm = NULL; editor = xmalloczero(sizeof(struct editor), "editor"); editor->browser = browser; editor->amendment = amendment; /* main window, centered in its browser */ window_rect(browser->win, &bounds); bounds.top += MBarHeight; InsetRect(&bounds, 2, 2); off = (bounds.bottom - bounds.top) / 5; bounds.top += off; bounds.bottom -= off; memcpy(&filename, browser->repo->bile->filename, sizeof(filename)); PtoCstr(filename); snprintf((char *)&title, sizeof(title), "%s: %s: Edit Amendment %d", PROGRAM_NAME, (browser->repo ? (char *)filename : "No repo open"), amendment->id); editor->win = NewWindow(0L, &bounds, CtoPstr(title), false, noGrowDocProc, (WindowPtr)-1L, true, 0); if (!editor) err(1, "Can't create editor window"); SetPort(editor->win); /* author */ bounds.top = PADDING; bounds.left = 55; fh = FontHeight(LABEL_FONT, LABEL_FONT_SIZE); bounds.bottom = bounds.top + fh + 4; bounds.right = 140; te_bounds = bounds; InsetRect(&te_bounds, 2, 2); TextFont(LABEL_FONT); TextSize(LABEL_FONT_SIZE); editor->author_te = TENew(&te_bounds, &bounds); TEAutoView(true, editor->author_te); TEActivate(editor->author_te); TEInsert(amendment->author, strlen(amendment->author), editor->author_te); /* date */ bounds.top = bounds.bottom + PADDING; bounds.bottom = bounds.top + fh + 2; bounds.right = 200; te_bounds = bounds; InsetRect(&te_bounds, 2, 2); TextFont(LABEL_FONT); TextSize(LABEL_FONT_SIZE); editor->date_te = TENew(&te_bounds, &bounds); TEAutoView(true, editor->date_te); ttm = localtime(&amendment->date); snprintf(date, sizeof(date), "%04d-%02d-%02d %02d:%02d:%02d", ttm->tm_year + 1900, ttm->tm_mon + 1, ttm->tm_mday, ttm->tm_hour, ttm->tm_min, ttm->tm_sec); TEInsert(date, strlen(date), editor->date_te); /* log message */ bounds.top = bounds.bottom + PADDING; bounds.bottom = editor->win->portRect.bottom - 20 - (PADDING * 2); bounds.right = editor->win->portRect.right - SCROLLBAR_WIDTH - PADDING; te_bounds = bounds; InsetRect(&te_bounds, 2, 2); TextFont(monaco); TextSize(9); editor->log_te = TEStylNew(&te_bounds, &bounds); style.tsFont = monaco; style.tsSize = 9; TESetStyle(doFont | doSize, &style, false, editor->log_te); TEAutoView(true, editor->log_te); TETabEnable(editor->log_te); HLock(amendment->log); TEInsert(*(amendment->log), amendment->log_len, editor->log_te); HUnlock(amendment->log); /* scrollbar for log message */ bounds.left = bounds.right; bounds.right += SCROLLBAR_WIDTH; bounds.bottom++; bounds.top--; editor->log_scroller = NewControl(editor->win, &bounds, "\p", true, 1, 1, 1, scrollBarProc, 0L); /* save button */ bounds.left = editor->win->portRect.right - PADDING - 100; bounds.right = bounds.left + 100; bounds.bottom = editor->win->portRect.bottom - PADDING; bounds.top = bounds.bottom - 20; editor->save_button = NewControl(editor->win, &bounds, "\pSave", true, 1, 1, 1, pushButProc, 0L); UpdateScrollbarForTE(editor->win, editor->log_scroller, editor->log_te, false); editor->last_te = editor->author_te; focusable = xmalloczero(sizeof(struct focusable), "editor focusable"); focusable->cookie = editor; focusable->win = editor->win; focusable->modal = true; focusable->idle = editor_idle; focusable->update = editor_update; focusable->mouse_down = editor_mouse_down; focusable->key_down = editor_key_down; focusable->menu = editor_handle_menu; focusable->close = editor_close; focusable_add(focusable); } bool editor_close(struct focusable *focusable) { struct editor *editor = (struct editor *)focusable->cookie; TEDispose(editor->author_te); TEDispose(editor->date_te); TEDispose(editor->log_te); DisposeWindow(editor->win); xfree(&editor); return true; } void editor_idle(struct focusable *focusable, EventRecord *event) { struct editor *editor = (struct editor *)focusable->cookie; TEIdle(editor->last_te); } void editor_update(struct focusable *focusable, EventRecord *event) { Rect r; short what = -1; struct editor *editor = (struct editor *)focusable->cookie; if (event != NULL) what = event->what; switch (what) { case -1: case updateEvt: r = (*(editor->author_te))->viewRect; MoveTo(PADDING, r.top + FontHeight(LABEL_FONT, LABEL_FONT_SIZE) - 2); TextFont(LABEL_FONT); TextSize(LABEL_FONT_SIZE); DrawText("Author:", 0, 7); InsetRect(&r, -1, -1); FrameRect(&r); TEUpdate(&r, editor->author_te); r = (*(editor->date_te))->viewRect; MoveTo(PADDING, r.top + FontHeight(LABEL_FONT, LABEL_FONT_SIZE) - 2); TextFont(LABEL_FONT); TextSize(LABEL_FONT_SIZE); DrawText("Date:", 0, 5); InsetRect(&r, -1, -1); FrameRect(&r); TEUpdate(&r, editor->date_te); r = (*(editor->log_te))->viewRect; MoveTo(PADDING, r.top + FontHeight(LABEL_FONT, LABEL_FONT_SIZE) - 2); TextFont(LABEL_FONT); TextSize(LABEL_FONT_SIZE); DrawText("Log:", 0, 4); InsetRect(&r, -1, -1); FrameRect(&r); TEUpdate(&r, editor->log_te); editor_update_menu(editor); UpdtControl(editor->win, editor->win->visRgn); break; } } void editor_suspend(struct focusable *focusable) { struct editor *editor = (struct editor *)focusable->cookie; TEDeactivate(editor->author_te); TEDeactivate(editor->date_te); TEDeactivate(editor->log_te); } void editor_resume(struct focusable *focusable) { struct editor *editor = (struct editor *)focusable->cookie; TEActivate(editor->author_te); TEActivate(editor->date_te); TEActivate(editor->log_te); } void editor_key_down(struct focusable *focusable, EventRecord *event) { struct editor *editor = (struct editor *)focusable->cookie; char k; k = (event->message & charCodeMask); if (k == '\r' && (editor->last_te == editor->author_te || editor->last_te == editor->date_te)) return; TEKey(k, editor->last_te); if (editor->last_te == editor->log_te) UpdateScrollbarForTE(editor->win, editor->log_scroller, editor->last_te, false); editor_update_menu(editor); } void editor_mouse_down(struct focusable *focusable, EventRecord *event) { struct editor *editor = (struct editor *)focusable->cookie; Point p; ControlHandle control; Rect r; short val, adj; p = event->where; GlobalToLocal(&p); r = (*(editor->author_te))->viewRect; if (PtInRect(p, &r)) { if (editor->last_te != editor->author_te) { editor->last_te = editor->author_te; TEDeactivate(editor->date_te); TEDeactivate(editor->log_te); TEActivate(editor->author_te); } TEClick(p, ((event->modifiers & shiftKey) != 0), editor->author_te); editor_update_menu(editor); return; } r = (*(editor->date_te))->viewRect; if (PtInRect(p, &r)) { if (editor->last_te != editor->date_te) { editor->last_te = editor->date_te; TEDeactivate(editor->author_te); TEDeactivate(editor->log_te); TEActivate(editor->date_te); } TEClick(p, ((event->modifiers & shiftKey) != 0), editor->date_te); editor_update_menu(editor); return; } r = (*(editor->log_te))->viewRect; if (PtInRect(p, &r)) { if (editor->last_te != editor->log_te) { editor->last_te = editor->log_te; TEDeactivate(editor->author_te); TEDeactivate(editor->date_te); TEActivate(editor->log_te); } TEClick(p, ((event->modifiers & shiftKey) != 0), editor->log_te); editor_update_menu(editor); return; } switch (FindControl(p, editor->win, &control)) { case inButton: if (TrackControl(control, p, 0L) && control == editor->save_button) editor_save(editor); break; case inUpButton: case inDownButton: case inPageUp: case inPageDown: if (control == editor->log_scroller) SetTrackControlTE(editor->log_te); else break; TrackControl(control, p, TrackMouseDownInControl); break; case inThumb: val = GetCtlValue(control); if (TrackControl(control, p, 0L) == 0) break; adj = val - GetCtlValue(control); if (adj != 0) { val -= adj; if (control == editor->log_scroller) TEScroll(0, adj * TEGetHeight(0, 0, editor->log_te), editor->log_te); SetCtlValue(control, val); } break; } } void editor_update_menu(struct editor *editor) { if ((*(editor->last_te))->selStart == (*(editor->last_te))->selEnd) { DisableItem(edit_menu, EDIT_MENU_CUT_ID); DisableItem(edit_menu, EDIT_MENU_COPY_ID); } else { EnableItem(edit_menu, EDIT_MENU_CUT_ID); EnableItem(edit_menu, EDIT_MENU_COPY_ID); } if ((*(editor->last_te))->nLines > 0) EnableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID); else DisableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID); EnableItem(edit_menu, EDIT_MENU_PASTE_ID); DisableItem(repo_menu, REPO_MENU_ADD_FILE_ID); DisableItem(repo_menu, REPO_MENU_DISCARD_CHANGES_ID); DisableItem(repo_menu, REPO_MENU_APPLY_DIFF_ID); DisableItem(amendment_menu, AMENDMENT_MENU_EDIT_ID); DisableItem(amendment_menu, AMENDMENT_MENU_EXPORT_ID); EnableItem(repo_menu, 0); } bool editor_handle_menu(struct focusable *focusable, short menu, short item) { struct editor *editor = (struct editor *)focusable->cookie; switch (menu) { case EDIT_MENU_ID: switch (item) { case EDIT_MENU_CUT_ID: TECut(editor->last_te); editor_update_menu(editor); if (editor->last_te == editor->log_te) UpdateScrollbarForTE(editor->win, editor->log_scroller, editor->last_te, false); return true; case EDIT_MENU_COPY_ID: TECopy(editor->last_te); editor_update_menu(editor); return true; case EDIT_MENU_PASTE_ID: TEPaste(editor->last_te); if (editor->last_te == editor->log_te) UpdateScrollbarForTE(editor->win, editor->log_scroller, editor->last_te, false); editor_update_menu(editor); return true; case EDIT_MENU_SELECT_ALL_ID: TESetSelect(0, 1024 * 32, editor->last_te); editor_update_menu(editor); return true; } break; } return false; } void editor_save(struct editor *editor) { struct tm ttm; size_t len, size; time_t ts; short ret, yy, mm, dd, hh, min, ss, count = 0; char *date, *data; if ((*(editor->author_te))->teLength == 0) { warn("Author field cannot be blank"); return; } len = (*(editor->date_te))->teLength; if (len == 0) { warn("Date field cannot be blank"); return; } date = xmalloc(len + 1, "editor_save"); memcpy(date, *(*(editor->date_te))->hText, len); date[len] = '\0'; ret = sscanf(date, "%d-%d-%d %d:%d:%d%n", &yy, &mm, &dd, &hh, &min, &ss, &count); xfree(&date); if (ret != 6 || count < 11) { warn("Date must be in YYYY-MM-DD HH:MM:SS format"); return; } ttm.tm_year = yy - 1900; ttm.tm_mon = mm - 1; ttm.tm_mday = dd; ttm.tm_hour = hh; ttm.tm_min = min; ttm.tm_sec = ss; ts = mktime(&ttm); len = (*(editor->log_te))->teLength; if (len == 0) { warn("Log cannot be blank"); return; } editor->amendment->date = ts; len = sizeof(editor->amendment->author) - 1; if ((*(editor->author_te))->teLength < len) len = (*(editor->author_te))->teLength; memcpy(editor->amendment->author, *(*(editor->author_te))->hText, len); editor->amendment->author[len] = '\0'; editor->amendment->log_len = (*(editor->log_te))->teLength; if (editor->amendment->log) DisposHandle(editor->amendment->log); editor->amendment->log = xNewHandle(editor->amendment->log_len); memcpy(*(editor->amendment->log), *(*(editor->log_te))->hText, editor->amendment->log_len); progress("Storing updated amendment metadata..."); repo_marshall_amendment(editor->amendment, &data, &len); size = bile_write(editor->browser->repo->bile, REPO_AMENDMENT_RTYPE, editor->amendment->id, data, len); if (size != len) panic("Failed storing amendment in repo file: %d", bile_error(editor->browser->repo->bile)); xfree(&data); editor->browser->need_refresh = true; focusable_close(focusable_find(editor->win)); progress(NULL); }