Download
jcs
/amend
/editor.c
(View History)
jcs browser: Limit initial window size to about 81 characters in committer view | Latest amendment: 96 on 2022-09-06 |
1 | /* |
2 | * Copyright (c) 2022 joshua stein <jcs@jcs.org> |
3 | * |
4 | * Permission to use, copy, modify, and distribute this software for any |
5 | * purpose with or without fee is hereby granted, provided that the above |
6 | * copyright notice and this permission notice appear in all copies. |
7 | * |
8 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
9 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
10 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
11 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
12 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
13 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
14 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
15 | */ |
16 | |
17 | #include <stdarg.h> |
18 | #include <stdio.h> |
19 | #include <string.h> |
20 | |
21 | #include <time.h> |
22 | #include "amend.h" |
23 | #include "browser.h" |
24 | #include "editor.h" |
25 | #include "focusable.h" |
26 | #include "repo.h" |
27 | #include "tetab.h" |
28 | #include "util.h" |
29 | |
30 | #define LABEL_FONT geneva |
31 | #define LABEL_FONT_SIZE 10 |
32 | |
33 | #define PADDING 10 |
34 | |
35 | bool editor_close(struct focusable *focusable); |
36 | void editor_idle(struct focusable *focusable, EventRecord *event); |
37 | void editor_update(struct focusable *focusable, EventRecord *event); |
38 | void editor_suspend(struct focusable *focusable); |
39 | void editor_resume(struct focusable *focusable); |
40 | void editor_key_down(struct focusable *focusable, EventRecord *event); |
41 | void editor_mouse_down(struct focusable *focusable, EventRecord *event); |
42 | bool editor_handle_menu(struct focusable *focusable, short menu, |
43 | short item); |
44 | |
45 | void editor_update_menu(struct editor *editor); |
46 | void editor_save(struct editor *editor); |
47 | |
48 | void |
49 | editor_init(struct browser *browser, struct repo_amendment *amendment) |
50 | { |
51 | Str255 title, filename; |
52 | struct editor *editor; |
53 | struct focusable *focusable; |
54 | char date[32]; |
55 | Rect bounds = { 0 }, te_bounds = { 0 }; |
56 | TextStyle style; |
57 | short fh, off; |
58 | struct tm *ttm = NULL; |
59 | |
60 | editor = xmalloczero(sizeof(struct editor), "editor"); |
61 | editor->browser = browser; |
62 | editor->amendment = amendment; |
63 | |
64 | /* main window, centered in its browser */ |
65 | window_rect(browser->win, &bounds); |
66 | bounds.top += MBarHeight; |
67 | InsetRect(&bounds, 2, 2); |
68 | off = (bounds.bottom - bounds.top) / 5; |
69 | bounds.top += off; |
70 | bounds.bottom -= off; |
71 | |
72 | memcpy(filename, browser->repo->bile->filename, sizeof(filename)); |
73 | PtoCstr(filename); |
74 | snprintf((char *)&title, sizeof(title), "%s: %s: Edit Amendment %d", |
75 | PROGRAM_NAME, (browser->repo ? (char *)filename : "No repo open"), |
76 | amendment->id); |
77 | |
78 | editor->win = NewWindow(0L, &bounds, CtoPstr(title), false, |
79 | noGrowDocProc, (WindowPtr)-1L, true, 0); |
80 | if (!editor) |
81 | err(1, "Can't create editor window"); |
82 | SetPort(editor->win); |
83 | |
84 | /* author */ |
85 | bounds.top = PADDING; |
86 | bounds.left = 55; |
87 | fh = FontHeight(LABEL_FONT, LABEL_FONT_SIZE); |
88 | bounds.bottom = bounds.top + fh + 4; |
89 | bounds.right = 140; |
90 | te_bounds = bounds; |
91 | InsetRect(&te_bounds, 2, 2); |
92 | TextFont(LABEL_FONT); |
93 | TextSize(LABEL_FONT_SIZE); |
94 | editor->author_te = TENew(&te_bounds, &bounds); |
95 | TEAutoView(true, editor->author_te); |
96 | TEActivate(editor->author_te); |
97 | TEInsert(amendment->author, strlen(amendment->author), |
98 | editor->author_te); |
99 | |
100 | /* date */ |
101 | bounds.top = bounds.bottom + PADDING; |
102 | bounds.bottom = bounds.top + fh + 2; |
103 | bounds.right = 200; |
104 | te_bounds = bounds; |
105 | InsetRect(&te_bounds, 2, 2); |
106 | TextFont(LABEL_FONT); |
107 | TextSize(LABEL_FONT_SIZE); |
108 | editor->date_te = TENew(&te_bounds, &bounds); |
109 | TEAutoView(true, editor->date_te); |
110 | |
111 | ttm = localtime(&amendment->date); |
112 | snprintf(date, sizeof(date), "%04d-%02d-%02d %02d:%02d:%02d", |
113 | ttm->tm_year + 1900, ttm->tm_mon + 1, ttm->tm_mday, |
114 | ttm->tm_hour, ttm->tm_min, ttm->tm_sec); |
115 | TEInsert(date, strlen(date), editor->date_te); |
116 | |
117 | /* log message */ |
118 | bounds.top = bounds.bottom + PADDING; |
119 | fh = FontHeight(monaco, 9); |
120 | bounds.bottom = editor->win->portRect.bottom - 20 - (PADDING * 2); |
121 | bounds.right = editor->win->portRect.right - SCROLLBAR_WIDTH - |
122 | PADDING; |
123 | te_bounds = bounds; |
124 | InsetRect(&te_bounds, 2, 2); |
125 | TextFont(monaco); |
126 | TextSize(9); |
127 | editor->log_te = TEStylNew(&te_bounds, &bounds); |
128 | style.tsFont = monaco; |
129 | style.tsSize = 9; |
130 | TESetStyle(doFont | doSize, &style, false, editor->log_te); |
131 | TEAutoView(true, editor->log_te); |
132 | TETabEnable(editor->log_te); |
133 | HLock(amendment->log); |
134 | TEInsert(*(amendment->log), amendment->log_len, editor->log_te); |
135 | HUnlock(amendment->log); |
136 | |
137 | /* scrollbar for log message */ |
138 | bounds.left = bounds.right; |
139 | bounds.right += SCROLLBAR_WIDTH; |
140 | bounds.bottom++; |
141 | bounds.top--; |
142 | editor->log_scroller = NewControl(editor->win, &bounds, "\p", |
143 | true, 1, 1, 1, scrollBarProc, 0L); |
144 | |
145 | /* save button */ |
146 | bounds.left = editor->win->portRect.right - PADDING - 100; |
147 | bounds.right = bounds.left + 100; |
148 | bounds.bottom = editor->win->portRect.bottom - PADDING; |
149 | bounds.top = bounds.bottom - 20; |
150 | editor->save_button = NewControl(editor->win, &bounds, "\pSave", |
151 | true, 1, 1, 1, pushButProc, 0L); |
152 | |
153 | editor->last_te = editor->author_te; |
154 | |
155 | focusable = xmalloczero(sizeof(struct focusable), "editor focusable"); |
156 | focusable->cookie = editor; |
157 | focusable->win = editor->win; |
158 | focusable->modal = true; |
159 | focusable->idle = editor_idle; |
160 | focusable->update = editor_update; |
161 | focusable->mouse_down = editor_mouse_down; |
162 | focusable->key_down = editor_key_down; |
163 | focusable->menu = editor_handle_menu; |
164 | focusable->close = editor_close; |
165 | focusable_add(focusable); |
166 | } |
167 | |
168 | bool |
169 | editor_close(struct focusable *focusable) |
170 | { |
171 | struct editor *editor = (struct editor *)focusable->cookie; |
172 | |
173 | TEDispose(editor->author_te); |
174 | TEDispose(editor->date_te); |
175 | TEDispose(editor->log_te); |
176 | DisposeWindow(editor->win); |
177 | |
178 | xfree(&editor); |
179 | |
180 | return true; |
181 | } |
182 | |
183 | void |
184 | editor_idle(struct focusable *focusable, EventRecord *event) |
185 | { |
186 | struct editor *editor = (struct editor *)focusable->cookie; |
187 | |
188 | TEIdle(editor->last_te); |
189 | } |
190 | |
191 | void |
192 | editor_update(struct focusable *focusable, EventRecord *event) |
193 | { |
194 | Str255 buf; |
195 | Rect r; |
196 | short what = -1, len; |
197 | struct editor *editor = (struct editor *)focusable->cookie; |
198 | |
199 | if (event != NULL) |
200 | what = event->what; |
201 | |
202 | switch (what) { |
203 | case -1: |
204 | case updateEvt: |
205 | r = (*(editor->author_te))->viewRect; |
206 | MoveTo(PADDING, r.top + FontHeight(LABEL_FONT, LABEL_FONT_SIZE) - 2); |
207 | TextFont(LABEL_FONT); |
208 | TextSize(LABEL_FONT_SIZE); |
209 | DrawText("Author:", 0, 7); |
210 | InsetRect(&r, -1, -1); |
211 | FrameRect(&r); |
212 | TEUpdate(&r, editor->author_te); |
213 | |
214 | r = (*(editor->date_te))->viewRect; |
215 | MoveTo(PADDING, r.top + FontHeight(LABEL_FONT, LABEL_FONT_SIZE) - 2); |
216 | TextFont(LABEL_FONT); |
217 | TextSize(LABEL_FONT_SIZE); |
218 | DrawText("Date:", 0, 5); |
219 | InsetRect(&r, -1, -1); |
220 | FrameRect(&r); |
221 | TEUpdate(&r, editor->date_te); |
222 | |
223 | r = (*(editor->log_te))->viewRect; |
224 | MoveTo(PADDING, r.top + FontHeight(LABEL_FONT, LABEL_FONT_SIZE) - 2); |
225 | TextFont(LABEL_FONT); |
226 | TextSize(LABEL_FONT_SIZE); |
227 | DrawText("Log:", 0, 4); |
228 | InsetRect(&r, -1, -1); |
229 | FrameRect(&r); |
230 | TEUpdate(&r, editor->log_te); |
231 | |
232 | editor_update_menu(editor); |
233 | UpdtControl(editor->win, editor->win->visRgn); |
234 | |
235 | break; |
236 | } |
237 | } |
238 | |
239 | void |
240 | editor_suspend(struct focusable *focusable) |
241 | { |
242 | struct editor *editor = (struct editor *)focusable->cookie; |
243 | |
244 | TEDeactivate(editor->author_te); |
245 | TEDeactivate(editor->date_te); |
246 | TEDeactivate(editor->log_te); |
247 | } |
248 | |
249 | void |
250 | editor_resume(struct focusable *focusable) |
251 | { |
252 | struct editor *editor = (struct editor *)focusable->cookie; |
253 | |
254 | TEActivate(editor->author_te); |
255 | TEActivate(editor->date_te); |
256 | TEActivate(editor->log_te); |
257 | } |
258 | |
259 | void |
260 | editor_key_down(struct focusable *focusable, EventRecord *event) |
261 | { |
262 | struct editor *editor = (struct editor *)focusable->cookie; |
263 | char k; |
264 | |
265 | k = (event->message & charCodeMask); |
266 | |
267 | if (k == '\r' && (editor->last_te == editor->author_te || |
268 | editor->last_te == editor->date_te)) |
269 | return; |
270 | |
271 | TEKey(k, editor->last_te); |
272 | if (editor->last_te == editor->log_te) |
273 | UpdateScrollbarForTE(editor->win, editor->log_scroller, |
274 | editor->last_te, false); |
275 | editor_update_menu(editor); |
276 | } |
277 | |
278 | void |
279 | editor_mouse_down(struct focusable *focusable, EventRecord *event) |
280 | { |
281 | struct editor *editor = (struct editor *)focusable->cookie; |
282 | Point p; |
283 | ControlHandle control; |
284 | Rect r; |
285 | short val, adj, page, was_selected, part, i; |
286 | |
287 | p = event->where; |
288 | GlobalToLocal(&p); |
289 | |
290 | r = (*(editor->author_te))->viewRect; |
291 | if (PtInRect(p, &r)) { |
292 | if (editor->last_te != editor->author_te) { |
293 | editor->last_te = editor->author_te; |
294 | TEDeactivate(editor->date_te); |
295 | TEDeactivate(editor->log_te); |
296 | TEActivate(editor->author_te); |
297 | } |
298 | TEClick(p, ((event->modifiers & shiftKey) != 0), editor->author_te); |
299 | editor_update_menu(editor); |
300 | return; |
301 | } |
302 | |
303 | |
304 | r = (*(editor->date_te))->viewRect; |
305 | if (PtInRect(p, &r)) { |
306 | if (editor->last_te != editor->date_te) { |
307 | editor->last_te = editor->date_te; |
308 | TEDeactivate(editor->author_te); |
309 | TEDeactivate(editor->log_te); |
310 | TEActivate(editor->date_te); |
311 | } |
312 | TEClick(p, ((event->modifiers & shiftKey) != 0), editor->date_te); |
313 | editor_update_menu(editor); |
314 | return; |
315 | } |
316 | |
317 | r = (*(editor->log_te))->viewRect; |
318 | if (PtInRect(p, &r)) { |
319 | if (editor->last_te != editor->log_te) { |
320 | editor->last_te = editor->log_te; |
321 | TEDeactivate(editor->author_te); |
322 | TEDeactivate(editor->date_te); |
323 | TEActivate(editor->log_te); |
324 | } |
325 | TEClick(p, ((event->modifiers & shiftKey) != 0), editor->log_te); |
326 | editor_update_menu(editor); |
327 | return; |
328 | } |
329 | |
330 | switch (part = FindControl(p, editor->win, &control)) { |
331 | case inButton: |
332 | if (TrackControl(control, p, 0L) && |
333 | control == editor->save_button) |
334 | editor_save(editor); |
335 | break; |
336 | case inUpButton: |
337 | case inDownButton: |
338 | case inPageUp: |
339 | case inPageDown: |
340 | if (control == editor->log_scroller) |
341 | SetTrackControlTE(editor->log_te); |
342 | else |
343 | break; |
344 | TrackControl(control, p, TrackMouseDownInControl); |
345 | break; |
346 | case inThumb: |
347 | val = GetCtlValue(control); |
348 | if (TrackControl(control, p, 0L) == 0) |
349 | break; |
350 | adj = val - GetCtlValue(control); |
351 | if (adj != 0) { |
352 | val -= adj; |
353 | if (control == editor->log_scroller) |
354 | TEScroll(0, adj * TEGetHeight(0, 0, editor->log_te), |
355 | editor->log_te); |
356 | SetCtlValue(control, val); |
357 | } |
358 | break; |
359 | } |
360 | } |
361 | |
362 | void |
363 | editor_update_menu(struct editor *editor) |
364 | { |
365 | if ((*(editor->last_te))->selStart == (*(editor->last_te))->selEnd) { |
366 | DisableItem(edit_menu, EDIT_MENU_CUT_ID); |
367 | DisableItem(edit_menu, EDIT_MENU_COPY_ID); |
368 | } else { |
369 | EnableItem(edit_menu, EDIT_MENU_CUT_ID); |
370 | EnableItem(edit_menu, EDIT_MENU_COPY_ID); |
371 | } |
372 | if ((*(editor->last_te))->nLines > 0) |
373 | EnableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID); |
374 | else |
375 | DisableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID); |
376 | EnableItem(edit_menu, EDIT_MENU_PASTE_ID); |
377 | |
378 | DisableItem(repo_menu, REPO_MENU_ADD_FILE_ID); |
379 | DisableItem(repo_menu, REPO_MENU_DISCARD_CHANGES_ID); |
380 | DisableItem(repo_menu, REPO_MENU_APPLY_PATCH_ID); |
381 | |
382 | DisableItem(amendment_menu, AMENDMENT_MENU_EDIT_ID); |
383 | DisableItem(amendment_menu, AMENDMENT_MENU_EXPORT_ID); |
384 | |
385 | EnableItem(repo_menu, 0); |
386 | } |
387 | |
388 | bool |
389 | editor_handle_menu(struct focusable *focusable, short menu, short item) |
390 | { |
391 | struct editor *editor = (struct editor *)focusable->cookie; |
392 | |
393 | switch (menu) { |
394 | case EDIT_MENU_ID: |
395 | switch (item) { |
396 | case EDIT_MENU_CUT_ID: |
397 | TECut(editor->last_te); |
398 | editor_update_menu(editor); |
399 | return true; |
400 | case EDIT_MENU_COPY_ID: |
401 | TECopy(editor->last_te); |
402 | editor_update_menu(editor); |
403 | return true; |
404 | case EDIT_MENU_PASTE_ID: |
405 | TEPaste(editor->last_te); |
406 | editor_update_menu(editor); |
407 | return true; |
408 | case EDIT_MENU_SELECT_ALL_ID: |
409 | TESetSelect(0, 1024 * 32, editor->last_te); |
410 | editor_update_menu(editor); |
411 | return true; |
412 | } |
413 | break; |
414 | } |
415 | |
416 | return false; |
417 | } |
418 | |
419 | void |
420 | editor_save(struct editor *editor) |
421 | { |
422 | struct tm ttm; |
423 | size_t len, size; |
424 | time_t ts; |
425 | short ret, yy, mm, dd, hh, min, ss, count = 0; |
426 | char *date, *author, *log, *data; |
427 | |
428 | if ((*(editor->author_te))->teLength == 0) { |
429 | warn("Author field cannot be blank"); |
430 | return; |
431 | } |
432 | |
433 | len = (*(editor->date_te))->teLength; |
434 | if (len == 0) { |
435 | warn("Date field cannot be blank"); |
436 | return; |
437 | } |
438 | |
439 | date = xmalloc(len + 1, "editor_save"); |
440 | memcpy(date, *(*(editor->date_te))->hText, len); |
441 | date[len] = '\0'; |
442 | |
443 | ret = sscanf(date, "%d-%d-%d %d:%d:%d%n", &yy, &mm, &dd, &hh, &min, |
444 | &ss, &count); |
445 | xfree(&date); |
446 | if (ret != 6 || count < 11) { |
447 | warn("Date must be in YYYY-MM-DD HH:MM:SS format"); |
448 | return; |
449 | } |
450 | |
451 | ttm.tm_year = yy - 1900; |
452 | ttm.tm_mon = mm - 1; |
453 | ttm.tm_mday = dd; |
454 | ttm.tm_hour = hh; |
455 | ttm.tm_min = min; |
456 | ttm.tm_sec = ss; |
457 | ts = mktime(&ttm); |
458 | |
459 | len = (*(editor->log_te))->teLength; |
460 | if (len == 0) { |
461 | warn("Log cannot be blank"); |
462 | return; |
463 | } |
464 | |
465 | editor->amendment->date = ts; |
466 | |
467 | len = sizeof(editor->amendment->author) - 1; |
468 | if ((*(editor->author_te))->teLength < len) |
469 | len = (*(editor->author_te))->teLength; |
470 | memcpy(editor->amendment->author, *(*(editor->author_te))->hText, |
471 | len); |
472 | editor->amendment->author[len] = '\0'; |
473 | |
474 | editor->amendment->log_len = (*(editor->log_te))->teLength; |
475 | if (editor->amendment->log) |
476 | DisposHandle(editor->amendment->log); |
477 | |
478 | editor->amendment->log = xNewHandle(editor->amendment->log_len); |
479 | memcpy(*(editor->amendment->log), *(*(editor->log_te))->hText, |
480 | editor->amendment->log_len); |
481 | |
482 | progress("Storing updated amendment metadata..."); |
483 | |
484 | repo_marshall_amendment(editor->amendment, &data, &len); |
485 | |
486 | size = bile_write(editor->browser->repo->bile, REPO_AMENDMENT_RTYPE, |
487 | editor->amendment->id, data, len); |
488 | if (size != len) |
489 | panic("Failed storing amendment in repo file: %d", |
490 | bile_error(editor->browser->repo->bile)); |
491 | xfree(&data); |
492 | |
493 | editor->browser->need_refresh = true; |
494 | focusable_close(focusable_find(editor->win)); |
495 | progress(NULL); |
496 | } |