AmendHub

Download

jcs

/

amend

/

editor.c

 

(View History)

jcs   committer+editor: Update log scrollbar after cut or paste Latest amendment: 114 on 2023-04-17

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 bounds.bottom = editor->win->portRect.bottom - 20 - (PADDING * 2);
120 bounds.right = editor->win->portRect.right - SCROLLBAR_WIDTH -
121 PADDING;
122 te_bounds = bounds;
123 InsetRect(&te_bounds, 2, 2);
124 TextFont(monaco);
125 TextSize(9);
126 editor->log_te = TEStylNew(&te_bounds, &bounds);
127 style.tsFont = monaco;
128 style.tsSize = 9;
129 TESetStyle(doFont | doSize, &style, false, editor->log_te);
130 TEAutoView(true, editor->log_te);
131 TETabEnable(editor->log_te);
132 HLock(amendment->log);
133 TEInsert(*(amendment->log), amendment->log_len, editor->log_te);
134 HUnlock(amendment->log);
135
136 /* scrollbar for log message */
137 bounds.left = bounds.right;
138 bounds.right += SCROLLBAR_WIDTH;
139 bounds.bottom++;
140 bounds.top--;
141 editor->log_scroller = NewControl(editor->win, &bounds, "\p",
142 true, 1, 1, 1, scrollBarProc, 0L);
143
144 /* save button */
145 bounds.left = editor->win->portRect.right - PADDING - 100;
146 bounds.right = bounds.left + 100;
147 bounds.bottom = editor->win->portRect.bottom - PADDING;
148 bounds.top = bounds.bottom - 20;
149 editor->save_button = NewControl(editor->win, &bounds, "\pSave",
150 true, 1, 1, 1, pushButProc, 0L);
151
152 editor->last_te = editor->author_te;
153
154 focusable = xmalloczero(sizeof(struct focusable), "editor focusable");
155 focusable->cookie = editor;
156 focusable->win = editor->win;
157 focusable->modal = true;
158 focusable->idle = editor_idle;
159 focusable->update = editor_update;
160 focusable->mouse_down = editor_mouse_down;
161 focusable->key_down = editor_key_down;
162 focusable->menu = editor_handle_menu;
163 focusable->close = editor_close;
164 focusable_add(focusable);
165 }
166
167 bool
168 editor_close(struct focusable *focusable)
169 {
170 struct editor *editor = (struct editor *)focusable->cookie;
171
172 TEDispose(editor->author_te);
173 TEDispose(editor->date_te);
174 TEDispose(editor->log_te);
175 DisposeWindow(editor->win);
176
177 xfree(&editor);
178
179 return true;
180 }
181
182 void
183 editor_idle(struct focusable *focusable, EventRecord *event)
184 {
185 struct editor *editor = (struct editor *)focusable->cookie;
186
187 TEIdle(editor->last_te);
188 }
189
190 void
191 editor_update(struct focusable *focusable, EventRecord *event)
192 {
193 Rect r;
194 short what = -1;
195 struct editor *editor = (struct editor *)focusable->cookie;
196
197 if (event != NULL)
198 what = event->what;
199
200 switch (what) {
201 case -1:
202 case updateEvt:
203 r = (*(editor->author_te))->viewRect;
204 MoveTo(PADDING, r.top + FontHeight(LABEL_FONT, LABEL_FONT_SIZE) - 2);
205 TextFont(LABEL_FONT);
206 TextSize(LABEL_FONT_SIZE);
207 DrawText("Author:", 0, 7);
208 InsetRect(&r, -1, -1);
209 FrameRect(&r);
210 TEUpdate(&r, editor->author_te);
211
212 r = (*(editor->date_te))->viewRect;
213 MoveTo(PADDING, r.top + FontHeight(LABEL_FONT, LABEL_FONT_SIZE) - 2);
214 TextFont(LABEL_FONT);
215 TextSize(LABEL_FONT_SIZE);
216 DrawText("Date:", 0, 5);
217 InsetRect(&r, -1, -1);
218 FrameRect(&r);
219 TEUpdate(&r, editor->date_te);
220
221 r = (*(editor->log_te))->viewRect;
222 MoveTo(PADDING, r.top + FontHeight(LABEL_FONT, LABEL_FONT_SIZE) - 2);
223 TextFont(LABEL_FONT);
224 TextSize(LABEL_FONT_SIZE);
225 DrawText("Log:", 0, 4);
226 InsetRect(&r, -1, -1);
227 FrameRect(&r);
228 TEUpdate(&r, editor->log_te);
229
230 editor_update_menu(editor);
231 UpdtControl(editor->win, editor->win->visRgn);
232
233 break;
234 }
235 }
236
237 void
238 editor_suspend(struct focusable *focusable)
239 {
240 struct editor *editor = (struct editor *)focusable->cookie;
241
242 TEDeactivate(editor->author_te);
243 TEDeactivate(editor->date_te);
244 TEDeactivate(editor->log_te);
245 }
246
247 void
248 editor_resume(struct focusable *focusable)
249 {
250 struct editor *editor = (struct editor *)focusable->cookie;
251
252 TEActivate(editor->author_te);
253 TEActivate(editor->date_te);
254 TEActivate(editor->log_te);
255 }
256
257 void
258 editor_key_down(struct focusable *focusable, EventRecord *event)
259 {
260 struct editor *editor = (struct editor *)focusable->cookie;
261 char k;
262
263 k = (event->message & charCodeMask);
264
265 if (k == '\r' && (editor->last_te == editor->author_te ||
266 editor->last_te == editor->date_te))
267 return;
268
269 TEKey(k, editor->last_te);
270 if (editor->last_te == editor->log_te)
271 UpdateScrollbarForTE(editor->win, editor->log_scroller,
272 editor->last_te, false);
273 editor_update_menu(editor);
274 }
275
276 void
277 editor_mouse_down(struct focusable *focusable, EventRecord *event)
278 {
279 struct editor *editor = (struct editor *)focusable->cookie;
280 Point p;
281 ControlHandle control;
282 Rect r;
283 short val, adj;
284
285 p = event->where;
286 GlobalToLocal(&p);
287
288 r = (*(editor->author_te))->viewRect;
289 if (PtInRect(p, &r)) {
290 if (editor->last_te != editor->author_te) {
291 editor->last_te = editor->author_te;
292 TEDeactivate(editor->date_te);
293 TEDeactivate(editor->log_te);
294 TEActivate(editor->author_te);
295 }
296 TEClick(p, ((event->modifiers & shiftKey) != 0), editor->author_te);
297 editor_update_menu(editor);
298 return;
299 }
300
301
302 r = (*(editor->date_te))->viewRect;
303 if (PtInRect(p, &r)) {
304 if (editor->last_te != editor->date_te) {
305 editor->last_te = editor->date_te;
306 TEDeactivate(editor->author_te);
307 TEDeactivate(editor->log_te);
308 TEActivate(editor->date_te);
309 }
310 TEClick(p, ((event->modifiers & shiftKey) != 0), editor->date_te);
311 editor_update_menu(editor);
312 return;
313 }
314
315 r = (*(editor->log_te))->viewRect;
316 if (PtInRect(p, &r)) {
317 if (editor->last_te != editor->log_te) {
318 editor->last_te = editor->log_te;
319 TEDeactivate(editor->author_te);
320 TEDeactivate(editor->date_te);
321 TEActivate(editor->log_te);
322 }
323 TEClick(p, ((event->modifiers & shiftKey) != 0), editor->log_te);
324 editor_update_menu(editor);
325 return;
326 }
327
328 switch (FindControl(p, editor->win, &control)) {
329 case inButton:
330 if (TrackControl(control, p, 0L) &&
331 control == editor->save_button)
332 editor_save(editor);
333 break;
334 case inUpButton:
335 case inDownButton:
336 case inPageUp:
337 case inPageDown:
338 if (control == editor->log_scroller)
339 SetTrackControlTE(editor->log_te);
340 else
341 break;
342 TrackControl(control, p, TrackMouseDownInControl);
343 break;
344 case inThumb:
345 val = GetCtlValue(control);
346 if (TrackControl(control, p, 0L) == 0)
347 break;
348 adj = val - GetCtlValue(control);
349 if (adj != 0) {
350 val -= adj;
351 if (control == editor->log_scroller)
352 TEScroll(0, adj * TEGetHeight(0, 0, editor->log_te),
353 editor->log_te);
354 SetCtlValue(control, val);
355 }
356 break;
357 }
358 }
359
360 void
361 editor_update_menu(struct editor *editor)
362 {
363 if ((*(editor->last_te))->selStart == (*(editor->last_te))->selEnd) {
364 DisableItem(edit_menu, EDIT_MENU_CUT_ID);
365 DisableItem(edit_menu, EDIT_MENU_COPY_ID);
366 } else {
367 EnableItem(edit_menu, EDIT_MENU_CUT_ID);
368 EnableItem(edit_menu, EDIT_MENU_COPY_ID);
369 }
370 if ((*(editor->last_te))->nLines > 0)
371 EnableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID);
372 else
373 DisableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID);
374 EnableItem(edit_menu, EDIT_MENU_PASTE_ID);
375
376 DisableItem(repo_menu, REPO_MENU_ADD_FILE_ID);
377 DisableItem(repo_menu, REPO_MENU_DISCARD_CHANGES_ID);
378 DisableItem(repo_menu, REPO_MENU_APPLY_DIFF_ID);
379
380 DisableItem(amendment_menu, AMENDMENT_MENU_EDIT_ID);
381 DisableItem(amendment_menu, AMENDMENT_MENU_EXPORT_ID);
382
383 EnableItem(repo_menu, 0);
384 }
385
386 bool
387 editor_handle_menu(struct focusable *focusable, short menu, short item)
388 {
389 struct editor *editor = (struct editor *)focusable->cookie;
390
391 switch (menu) {
392 case EDIT_MENU_ID:
393 switch (item) {
394 case EDIT_MENU_CUT_ID:
395 TECut(editor->last_te);
396 editor_update_menu(editor);
397 if (editor->last_te == editor->log_te)
398 UpdateScrollbarForTE(editor->win, editor->log_scroller,
399 editor->last_te, false);
400 return true;
401 case EDIT_MENU_COPY_ID:
402 TECopy(editor->last_te);
403 editor_update_menu(editor);
404 return true;
405 case EDIT_MENU_PASTE_ID:
406 TEPaste(editor->last_te);
407 if (editor->last_te == editor->log_te)
408 UpdateScrollbarForTE(editor->win, editor->log_scroller,
409 editor->last_te, false);
410 editor_update_menu(editor);
411 return true;
412 case EDIT_MENU_SELECT_ALL_ID:
413 TESetSelect(0, 1024 * 32, editor->last_te);
414 editor_update_menu(editor);
415 return true;
416 }
417 break;
418 }
419
420 return false;
421 }
422
423 void
424 editor_save(struct editor *editor)
425 {
426 struct tm ttm;
427 size_t len, size;
428 time_t ts;
429 short ret, yy, mm, dd, hh, min, ss, count = 0;
430 char *date, *data;
431
432 if ((*(editor->author_te))->teLength == 0) {
433 warn("Author field cannot be blank");
434 return;
435 }
436
437 len = (*(editor->date_te))->teLength;
438 if (len == 0) {
439 warn("Date field cannot be blank");
440 return;
441 }
442
443 date = xmalloc(len + 1, "editor_save");
444 memcpy(date, *(*(editor->date_te))->hText, len);
445 date[len] = '\0';
446
447 ret = sscanf(date, "%d-%d-%d %d:%d:%d%n", &yy, &mm, &dd, &hh, &min,
448 &ss, &count);
449 xfree(&date);
450 if (ret != 6 || count < 11) {
451 warn("Date must be in YYYY-MM-DD HH:MM:SS format");
452 return;
453 }
454
455 ttm.tm_year = yy - 1900;
456 ttm.tm_mon = mm - 1;
457 ttm.tm_mday = dd;
458 ttm.tm_hour = hh;
459 ttm.tm_min = min;
460 ttm.tm_sec = ss;
461 ts = mktime(&ttm);
462
463 len = (*(editor->log_te))->teLength;
464 if (len == 0) {
465 warn("Log cannot be blank");
466 return;
467 }
468
469 editor->amendment->date = ts;
470
471 len = sizeof(editor->amendment->author) - 1;
472 if ((*(editor->author_te))->teLength < len)
473 len = (*(editor->author_te))->teLength;
474 memcpy(editor->amendment->author, *(*(editor->author_te))->hText,
475 len);
476 editor->amendment->author[len] = '\0';
477
478 editor->amendment->log_len = (*(editor->log_te))->teLength;
479 if (editor->amendment->log)
480 DisposHandle(editor->amendment->log);
481
482 editor->amendment->log = xNewHandle(editor->amendment->log_len);
483 memcpy(*(editor->amendment->log), *(*(editor->log_te))->hText,
484 editor->amendment->log_len);
485
486 progress("Storing updated amendment metadata...");
487
488 repo_marshall_amendment(editor->amendment, &data, &len);
489
490 size = bile_write(editor->browser->repo->bile, REPO_AMENDMENT_RTYPE,
491 editor->amendment->id, data, len);
492 if (size != len)
493 panic("Failed storing amendment in repo file: %d",
494 bile_error(editor->browser->repo->bile));
495 xfree(&data);
496
497 editor->browser->need_refresh = true;
498 focusable_close(focusable_find(editor->win));
499 progress(NULL);
500 }