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