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 | } |