AmendHub

Download

jcs

/

amend

/

editor.c

 

(View History)

jcs   *: Add author e-mail to settings and amendments Latest amendment: 181 on 2026-10-01

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 "util.h"
28
29 #define LABEL_FONT geneva
30 #define LABEL_FONT_SIZE 10
31
32 #define PADDING 10
33
34 bool editor_close(struct focusable *focusable);
35 void editor_idle(struct focusable *focusable, EventRecord *event);
36 void editor_update(struct focusable *focusable, EventRecord *event);
37 void editor_suspend(struct focusable *focusable);
38 void editor_resume(struct focusable *focusable);
39 void editor_key_down(struct focusable *focusable, EventRecord *event);
40 void editor_mouse_down(struct focusable *focusable, EventRecord *event);
41 bool editor_handle_menu(struct focusable *focusable, short menu,
42 short item);
43
44 void editor_update_menu(struct editor *editor);
45 void editor_save(struct editor *editor);
46
47 void
48 editor_init(struct browser *browser, struct repo_amendment *amendment)
49 {
50 Str255 title, filename;
51 struct editor *editor;
52 struct focusable *focusable;
53 char date[32];
54 Rect bounds = { 0 }, te_bounds = { 0 };
55 TextStyle style;
56 short fh, off;
57 struct tm *ttm = NULL;
58
59 editor = xmalloczero(sizeof(struct editor));
60 editor->browser = browser;
61 editor->amendment = amendment;
62
63 /* main window, centered in its browser */
64 window_rect(browser->win, &bounds);
65 bounds.top += MBarHeight;
66 InsetRect(&bounds, 2, 2);
67 off = (bounds.bottom - bounds.top) / 5;
68 bounds.top += off;
69 bounds.bottom -= off;
70
71 memcpy(&filename, browser->repo->bile->filename, sizeof(filename));
72 PtoCstr(filename);
73 snprintf((char *)&title, sizeof(title), "%s: %s: Edit Amendment %d",
74 PROGRAM_NAME, (browser->repo ? (char *)filename : "No repo open"),
75 amendment->id);
76
77 editor->win = NewWindow(0L, &bounds, CtoPstr(title), false,
78 noGrowDocProc, (WindowPtr)-1L, true, 0);
79 if (!editor->win)
80 panic("Can't create editor window");
81 SetPort(editor->win);
82
83 /* author name */
84 bounds.top = PADDING;
85 bounds.left = 90;
86 fh = FontHeight(LABEL_FONT, LABEL_FONT_SIZE);
87 bounds.bottom = bounds.top + fh + 2;
88 bounds.right = 240;
89 te_bounds = bounds;
90 InsetRect(&te_bounds, 2, 2);
91 TextFont(LABEL_FONT);
92 TextSize(LABEL_FONT_SIZE);
93 editor->author_te = TENew(&te_bounds, &bounds);
94 TEAutoView(true, editor->author_te);
95 TEActivate(editor->author_te);
96 TEInsert(amendment->author, strlen(amendment->author),
97 editor->author_te);
98
99 /* author e-mail */
100 bounds.top = bounds.bottom + PADDING;
101 bounds.bottom = bounds.top + fh + 2;
102 bounds.right = 240;
103 te_bounds = bounds;
104 InsetRect(&te_bounds, 2, 2);
105 TextFont(LABEL_FONT);
106 TextSize(LABEL_FONT_SIZE);
107 editor->author_email_te = TENew(&te_bounds, &bounds);
108 TEAutoView(true, editor->author_email_te);
109 TEInsert(amendment->author_email, strlen(amendment->author_email),
110 editor->author_email_te);
111
112 /* date */
113 bounds.top = bounds.bottom + PADDING;
114 bounds.bottom = bounds.top + fh + 2;
115 bounds.right = 240;
116 te_bounds = bounds;
117 InsetRect(&te_bounds, 2, 2);
118 TextFont(LABEL_FONT);
119 TextSize(LABEL_FONT_SIZE);
120 editor->date_te = TENew(&te_bounds, &bounds);
121 TEAutoView(true, editor->date_te);
122
123 ttm = localtime(&amendment->date);
124 snprintf(date, sizeof(date), "%04d-%02d-%02d %02d:%02d:%02d",
125 ttm->tm_year + 1900, ttm->tm_mon + 1, ttm->tm_mday,
126 ttm->tm_hour, ttm->tm_min, ttm->tm_sec);
127 TEInsert(date, strlen(date), editor->date_te);
128
129 /* log message */
130 bounds.left = PADDING;
131 bounds.top = bounds.bottom + PADDING + PADDING + fh;
132 bounds.bottom = editor->win->portRect.bottom - 20 - (PADDING * 2);
133 bounds.right = editor->win->portRect.right - SCROLLBAR_WIDTH -
134 PADDING;
135 te_bounds = bounds;
136 InsetRect(&te_bounds, 2, 2);
137 TextFont(monaco);
138 TextSize(9);
139 editor->log_te = TEStylNew(&te_bounds, &bounds);
140 style.tsFont = monaco;
141 style.tsSize = 9;
142 TESetStyle(doFont | doSize, &style, false, editor->log_te);
143 TEAutoView(true, editor->log_te);
144 HLock(amendment->log);
145 TEInsert(*(amendment->log), amendment->log_len, editor->log_te);
146 HUnlock(amendment->log);
147
148 /* scrollbar for log message */
149 bounds.left = bounds.right;
150 bounds.right += SCROLLBAR_WIDTH;
151 bounds.bottom++;
152 bounds.top--;
153 editor->log_scroller = NewControl(editor->win, &bounds, "\p",
154 true, 1, 1, 1, scrollBarProc, 0L);
155
156 /* save button */
157 bounds.left = editor->win->portRect.right - PADDING - 100;
158 bounds.right = bounds.left + 100;
159 bounds.bottom = editor->win->portRect.bottom - PADDING;
160 bounds.top = bounds.bottom - 20;
161 editor->save_button = NewControl(editor->win, &bounds, "\pSave",
162 true, 1, 1, 1, pushButProc, 0L);
163
164 UpdateScrollbarForTE(editor->win, editor->log_scroller,
165 editor->log_te, false);
166
167 editor->last_te = editor->author_te;
168
169 focusable = xmalloczero(sizeof(struct focusable));
170 focusable->cookie = editor;
171 focusable->win = editor->win;
172 focusable->modal = true;
173 focusable->idle = editor_idle;
174 focusable->update = editor_update;
175 focusable->mouse_down = editor_mouse_down;
176 focusable->key_down = editor_key_down;
177 focusable->menu = editor_handle_menu;
178 focusable->close = editor_close;
179 focusable_add(focusable);
180 }
181
182 bool
183 editor_close(struct focusable *focusable)
184 {
185 struct editor *editor = (struct editor *)focusable->cookie;
186
187 TEDispose(editor->author_te);
188 TEDispose(editor->author_email_te);
189 TEDispose(editor->date_te);
190 TEDispose(editor->log_te);
191 DisposeWindow(editor->win);
192
193 xfree(&editor);
194 xfree(&focusable);
195
196 return true;
197 }
198
199 void
200 editor_idle(struct focusable *focusable, EventRecord *event)
201 {
202 struct editor *editor = (struct editor *)focusable->cookie;
203
204 TEIdle(editor->last_te);
205 }
206
207 void
208 editor_update(struct focusable *focusable, EventRecord *event)
209 {
210 Rect r;
211 short what = -1;
212 struct editor *editor = (struct editor *)focusable->cookie;
213
214 if (event != NULL)
215 what = event->what;
216
217 switch (what) {
218 case -1:
219 case updateEvt:
220 r = (*(editor->author_te))->viewRect;
221 MoveTo(PADDING, r.top + FontHeight(LABEL_FONT, LABEL_FONT_SIZE) - 2);
222 TextFont(LABEL_FONT);
223 TextSize(LABEL_FONT_SIZE);
224 DrawText("Author Name:", 0, 12);
225 InsetRect(&r, -1, -1);
226 FrameRect(&r);
227 TEUpdate(&r, editor->author_te);
228
229 r = (*(editor->author_email_te))->viewRect;
230 MoveTo(PADDING, r.top + FontHeight(LABEL_FONT, LABEL_FONT_SIZE) - 2);
231 TextFont(LABEL_FONT);
232 TextSize(LABEL_FONT_SIZE);
233 DrawText("Author E-Mail:", 0, 14);
234 InsetRect(&r, -1, -1);
235 FrameRect(&r);
236 TEUpdate(&r, editor->author_email_te);
237
238 r = (*(editor->date_te))->viewRect;
239 MoveTo(PADDING, r.top + FontHeight(LABEL_FONT, LABEL_FONT_SIZE) - 2);
240 TextFont(LABEL_FONT);
241 TextSize(LABEL_FONT_SIZE);
242 DrawText("Date:", 0, 5);
243 InsetRect(&r, -1, -1);
244 FrameRect(&r);
245 TEUpdate(&r, editor->date_te);
246
247 r = (*(editor->log_te))->viewRect;
248 MoveTo(PADDING, r.top -
249 FontHeight(LABEL_FONT, LABEL_FONT_SIZE) - 2);
250 TextFont(LABEL_FONT);
251 TextSize(LABEL_FONT_SIZE);
252 DrawText("Log:", 0, 4);
253 InsetRect(&r, -1, -1);
254 FrameRect(&r);
255 TEUpdate(&r, editor->log_te);
256
257 editor_update_menu(editor);
258 UpdtControl(editor->win, editor->win->visRgn);
259
260 break;
261 }
262 }
263
264 void
265 editor_suspend(struct focusable *focusable)
266 {
267 struct editor *editor = (struct editor *)focusable->cookie;
268
269 TEDeactivate(editor->last_te);
270 }
271
272 void
273 editor_resume(struct focusable *focusable)
274 {
275 struct editor *editor = (struct editor *)focusable->cookie;
276
277 TEActivate(editor->last_te);
278 }
279
280 void
281 editor_key_down(struct focusable *focusable, EventRecord *event)
282 {
283 struct editor *editor = (struct editor *)focusable->cookie;
284 char k;
285
286 k = (event->message & charCodeMask);
287
288 if (k == '\r' && editor->last_te != editor->log_te)
289 return;
290
291 TEKey(k, editor->last_te);
292 if (editor->last_te == editor->log_te)
293 UpdateScrollbarForTE(editor->win, editor->log_scroller,
294 editor->last_te, false);
295 editor_update_menu(editor);
296 }
297
298 void
299 editor_mouse_down(struct focusable *focusable, EventRecord *event)
300 {
301 struct editor *editor = (struct editor *)focusable->cookie;
302 Point p;
303 ControlHandle control;
304 Rect r;
305 short val, adj;
306
307 p = event->where;
308 GlobalToLocal(&p);
309
310 r = (*(editor->author_te))->viewRect;
311 if (PtInRect(p, &r)) {
312 if (editor->last_te != editor->author_te) {
313 editor->last_te = editor->author_te;
314 TEDeactivate(editor->author_email_te);
315 TEDeactivate(editor->date_te);
316 TEDeactivate(editor->log_te);
317 TEActivate(editor->author_te);
318 }
319 TEClick(p, ((event->modifiers & shiftKey) != 0), editor->author_te);
320 editor_update_menu(editor);
321 return;
322 }
323
324 r = (*(editor->author_email_te))->viewRect;
325 if (PtInRect(p, &r)) {
326 if (editor->last_te != editor->author_email_te) {
327 editor->last_te = editor->author_email_te;
328 TEDeactivate(editor->author_te);
329 TEDeactivate(editor->date_te);
330 TEDeactivate(editor->log_te);
331 TEActivate(editor->author_email_te);
332 }
333 TEClick(p, ((event->modifiers & shiftKey) != 0),
334 editor->author_email_te);
335 editor_update_menu(editor);
336 return;
337 }
338
339 r = (*(editor->date_te))->viewRect;
340 if (PtInRect(p, &r)) {
341 if (editor->last_te != editor->date_te) {
342 editor->last_te = editor->date_te;
343 TEDeactivate(editor->author_te);
344 TEDeactivate(editor->log_te);
345 TEActivate(editor->date_te);
346 }
347 TEClick(p, ((event->modifiers & shiftKey) != 0), editor->date_te);
348 editor_update_menu(editor);
349 return;
350 }
351
352 r = (*(editor->log_te))->viewRect;
353 if (PtInRect(p, &r)) {
354 if (editor->last_te != editor->log_te) {
355 editor->last_te = editor->log_te;
356 TEDeactivate(editor->author_te);
357 TEDeactivate(editor->date_te);
358 TEActivate(editor->log_te);
359 }
360 TEClick(p, ((event->modifiers & shiftKey) != 0), editor->log_te);
361 editor_update_menu(editor);
362 return;
363 }
364
365 switch (FindControl(p, editor->win, &control)) {
366 case inButton:
367 if (TrackControl(control, p, 0L) &&
368 control == editor->save_button)
369 editor_save(editor);
370 break;
371 case inUpButton:
372 case inDownButton:
373 case inPageUp:
374 case inPageDown:
375 if (control == editor->log_scroller)
376 SetTrackControlTE(editor->log_te);
377 else
378 break;
379 TrackControl(control, p, TrackMouseDownInControl);
380 break;
381 case inThumb:
382 val = GetCtlValue(control);
383 if (TrackControl(control, p, 0L) == 0)
384 break;
385 adj = val - GetCtlValue(control);
386 if (adj != 0) {
387 val -= adj;
388 if (control == editor->log_scroller)
389 TEScroll(0, adj * TEGetHeight(0, 0, editor->log_te),
390 editor->log_te);
391 SetCtlValue(control, val);
392 }
393 break;
394 }
395 }
396
397 void
398 editor_update_menu(struct editor *editor)
399 {
400 DisableItem(file_menu, FILE_MENU_DIFF_ID);
401 DisableItem(file_menu, FILE_MENU_CHECKOUT_ID);
402
403 if ((*(editor->last_te))->selStart == (*(editor->last_te))->selEnd) {
404 DisableItem(edit_menu, EDIT_MENU_CUT_ID);
405 DisableItem(edit_menu, EDIT_MENU_COPY_ID);
406 } else {
407 EnableItem(edit_menu, EDIT_MENU_CUT_ID);
408 EnableItem(edit_menu, EDIT_MENU_COPY_ID);
409 }
410 if ((*(editor->last_te))->nLines > 0)
411 EnableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID);
412 else
413 DisableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID);
414 EnableItem(edit_menu, EDIT_MENU_PASTE_ID);
415
416 DisableItem(repo_menu, REPO_MENU_ADD_FILE_ID);
417 DisableItem(repo_menu, REPO_MENU_APPLY_PATCH_ID);
418
419 DisableItem(amendment_menu, AMENDMENT_MENU_EDIT_ID);
420 DisableItem(amendment_menu, AMENDMENT_MENU_EXPORT_ID);
421 }
422
423 bool
424 editor_handle_menu(struct focusable *focusable, short menu, short item)
425 {
426 struct editor *editor = (struct editor *)focusable->cookie;
427
428 switch (menu) {
429 case EDIT_MENU_ID:
430 switch (item) {
431 case EDIT_MENU_CUT_ID:
432 TECut(editor->last_te);
433 editor_update_menu(editor);
434 if (editor->last_te == editor->log_te)
435 UpdateScrollbarForTE(editor->win, editor->log_scroller,
436 editor->last_te, false);
437 return true;
438 case EDIT_MENU_COPY_ID:
439 TECopy(editor->last_te);
440 editor_update_menu(editor);
441 return true;
442 case EDIT_MENU_PASTE_ID:
443 TEPaste(editor->last_te);
444 if (editor->last_te == editor->log_te)
445 UpdateScrollbarForTE(editor->win, editor->log_scroller,
446 editor->last_te, false);
447 editor_update_menu(editor);
448 return true;
449 case EDIT_MENU_SELECT_ALL_ID:
450 TESetSelect(0, 1024 * 32, editor->last_te);
451 editor_update_menu(editor);
452 return true;
453 }
454 break;
455 }
456
457 return false;
458 }
459
460 void
461 editor_save(struct editor *editor)
462 {
463 struct tm ttm;
464 size_t len, size;
465 time_t ts;
466 short ret, yy, mm, dd, hh, min, ss, count = 0;
467 char *date, *data;
468
469 if ((*(editor->author_te))->teLength == 0) {
470 warn("Author field cannot be blank");
471 return;
472 }
473
474 len = (*(editor->date_te))->teLength;
475 if (len == 0) {
476 warn("Date field cannot be blank");
477 return;
478 }
479
480 date = xmalloc(len + 1);
481 memcpy(date, *(*(editor->date_te))->hText, len);
482 date[len] = '\0';
483
484 ret = sscanf(date, "%d-%d-%d %d:%d:%d%n", &yy, &mm, &dd, &hh, &min,
485 &ss, &count);
486 xfree(&date);
487 if (ret != 6 || count < 11) {
488 warn("Date must be in YYYY-MM-DD HH:MM:SS format");
489 return;
490 }
491
492 memset(&ttm, 0, sizeof(ttm));
493 ttm.tm_year = yy - 1900;
494 ttm.tm_mon = mm - 1;
495 ttm.tm_mday = dd;
496 ttm.tm_hour = hh;
497 ttm.tm_min = min;
498 ttm.tm_sec = ss;
499 ttm.tm_isdst = -1;
500 if ((ts = mktime(&ttm)) == -1) {
501 warn("Invalid timestamp");
502 return;
503 }
504
505 len = (*(editor->log_te))->teLength;
506 if (len == 0) {
507 warn("Log cannot be blank");
508 return;
509 }
510
511 editor->amendment->date = ts;
512
513 len = sizeof(editor->amendment->author) - 1;
514 if ((*(editor->author_te))->teLength < len)
515 len = (*(editor->author_te))->teLength;
516 memcpy(editor->amendment->author, *(*(editor->author_te))->hText,
517 len);
518 editor->amendment->author[len] = '\0';
519
520 len = sizeof(editor->amendment->author_email) - 1;
521 if ((*(editor->author_email_te))->teLength < len)
522 len = (*(editor->author_email_te))->teLength;
523 memcpy(editor->amendment->author_email,
524 *(*(editor->author_email_te))->hText, len);
525 editor->amendment->author_email[len] = '\0';
526
527 editor->amendment->log_len = (*(editor->log_te))->teLength;
528 if (editor->amendment->log)
529 DisposHandle(editor->amendment->log);
530
531 editor->amendment->log = xNewHandle(editor->amendment->log_len);
532 memcpy(*(editor->amendment->log), *(*(editor->log_te))->hText,
533 editor->amendment->log_len);
534
535 progress("Storing updated amendment metadata...");
536
537 repo_marshall_amendment(editor->amendment, &data, &len);
538
539 size = bile_write(editor->browser->repo->bile, REPO_AMENDMENT_RTYPE,
540 editor->amendment->id, data, len);
541 if (size != len)
542 panic("Failed storing amendment in repo file: %d",
543 bile_error(editor->browser->repo->bile));
544 xfree(&data);
545
546 editor->browser->need_refresh = true;
547 focusable_close(focusable_find(editor->win));
548 progress(NULL);
549 }