xref: /plan9-contrib/sys/src/cmd/acme/wind.c (revision d46c239f8612929b7dbade67d0d071633df3a15d)
1 #include <u.h>
2 #include <libc.h>
3 #include <draw.h>
4 #include <thread.h>
5 #include <cursor.h>
6 #include <mouse.h>
7 #include <keyboard.h>
8 #include <frame.h>
9 #include <fcall.h>
10 #include <plumb.h>
11 #include "dat.h"
12 #include "fns.h"
13 
14 int	winid;
15 
16 void
17 wininit(Window *w, Window *clone, Rectangle r)
18 {
19 	Rectangle r1, br;
20 	File *f;
21 	Reffont *rf;
22 	Rune *rp;
23 	int nc;
24 
25 	w->tag.w = w;
26 	w->body.w = w;
27 	w->id = ++winid;
28 	incref(w);
29 	w->ctlfid = ~0;
30 	w->utflastqid = -1;
31 	r1 = r;
32 	r1.max.y = r1.min.y + font->height;
33 	incref(&reffont);
34 	f = fileaddtext(nil, &w->tag);
35 	textinit(&w->tag, f, r1, &reffont, tagcols);
36 	w->tag.what = Tag;
37 	/* tag is a copy of the contents, not a tracked image */
38 	if(clone){
39 		textdelete(&w->tag, 0, w->tag.file->nc, TRUE);
40 		nc = clone->tag.file->nc;
41 		rp = runemalloc(nc);
42 		bufread(clone->tag.file, 0, rp, nc);
43 		textinsert(&w->tag, 0, rp, nc, TRUE);
44 		free(rp);
45 		filereset(w->tag.file);
46 		textsetselect(&w->tag, nc, nc);
47 	}
48 	r1 = r;
49 	r1.min.y += font->height + 1;
50 	if(r1.max.y < r1.min.y)
51 		r1.max.y = r1.min.y;
52 	f = nil;
53 	if(clone){
54 		f = clone->body.file;
55 		w->body.org = clone->body.org;
56 		w->isscratch = clone->isscratch;
57 		rf = rfget(FALSE, FALSE, FALSE, clone->body.reffont->f->name);
58 	}else
59 		rf = rfget(FALSE, FALSE, FALSE, nil);
60 	f = fileaddtext(f, &w->body);
61 	w->body.what = Body;
62 	textinit(&w->body, f, r1, rf, textcols);
63 	r1.min.y -= 1;
64 	r1.max.y = r1.min.y+1;
65 	draw(screen, r1, tagcols[BORD], nil, ZP);
66 	textscrdraw(&w->body);
67 	w->r = r;
68 	w->r.max.y = w->body.r.max.y;
69 	br.min = w->tag.scrollr.min;
70 	br.max.x = br.min.x + Dx(button->r);
71 	br.max.y = br.min.y + Dy(button->r);
72 	draw(screen, br, button, nil, button->r.min);
73 	w->filemenu = TRUE;
74 	w->maxlines = w->body.maxlines;
75 	if(clone){
76 		w->dirty = clone->dirty;
77 		textsetselect(&w->body, clone->body.q0, clone->body.q1);
78 		winsettag(w);
79 	}
80 }
81 
82 int
83 winresize(Window *w, Rectangle r, int safe)
84 {
85 	Rectangle r1;
86 	int y;
87 	Image *b;
88 	Rectangle br;
89 
90 	r1 = r;
91 	r1.max.y = r1.min.y + font->height;
92 	y = r1.max.y;
93 	if(!safe || !eqrect(w->tag.r, r1)){
94 		y = textresize(&w->tag, r1);
95 		b = button;
96 		if(w->body.file->mod && !w->isdir && !w->isscratch)
97 			b = modbutton;
98 		br.min = w->tag.scrollr.min;
99 		br.max.x = br.min.x + Dx(b->r);
100 		br.max.y = br.min.y + Dy(b->r);
101 		draw(screen, br, b, nil, b->r.min);
102 	}
103 	if(!safe || !eqrect(w->body.r, r1)){
104 		if(y+1+font->height > r.max.y){		/* no body */
105 			r1.min.y = y;
106 			r1.max.y = y;
107 			textresize(&w->body, r1);
108 			w->r = r;
109 			w->r.max.y = y;
110 			return y;
111 		}
112 		r1 = r;
113 		r1.min.y = y;
114 		r1.max.y = y + 1;
115 		draw(screen, r1, tagcols[BORD], nil, ZP);
116 		r1.min.y = y + 1;
117 		r1.max.y = r.max.y;
118 		y = textresize(&w->body, r1);
119 		w->r = r;
120 		w->r.max.y = y;
121 		textscrdraw(&w->body);
122 	}
123 	w->maxlines = min(w->body.nlines, max(w->maxlines, w->body.maxlines));
124 	return w->r.max.y;
125 }
126 
127 void
128 winlock1(Window *w, int owner)
129 {
130 	incref(w);
131 	qlock(w);
132 	w->owner = owner;
133 }
134 
135 void
136 winlock(Window *w, int owner)
137 {
138 	int i;
139 	File *f;
140 
141 	f = w->body.file;
142 	for(i=0; i<f->ntext; i++)
143 		winlock1(f->text[i]->w, owner);
144 }
145 
146 void
147 winunlock(Window *w)
148 {
149 	int i;
150 	File *f;
151 
152 	f = w->body.file;
153 	for(i=0; i<f->ntext; i++){
154 		w = f->text[i]->w;
155 		w->owner = 0;
156 		qunlock(w);
157 		winclose(w);
158 		/* winclose() can change up f->text; beware */
159 		if(f->ntext>0 && w != f->text[i]->w)
160 			--i;	/* winclose() deleted window */
161 	}
162 }
163 
164 void
165 winmousebut(Window *w)
166 {
167 	moveto(mousectl, divpt(addpt(w->tag.scrollr.min, w->tag.scrollr.max), 2));
168 }
169 
170 void
171 windirfree(Window *w)
172 {
173 	int i;
174 	Dirlist *dl;
175 
176 	if(w->isdir){
177 		for(i=0; i<w->ndl; i++){
178 			dl = w->dlp[i];
179 			free(dl->r);
180 			free(dl);
181 		}
182 		free(w->dlp);
183 	}
184 	w->dlp = nil;
185 	w->ndl = 0;
186 }
187 
188 void
189 winclose(Window *w)
190 {
191 	int i;
192 
193 	if(decref(w) == 0){
194 		windirfree(w);
195 		textclose(&w->tag);
196 		textclose(&w->body);
197 		if(activewin == w)
198 			activewin = nil;
199 		for(i=0; i<w->nincl; i++)
200 			free(w->incl[i]);
201 		free(w->incl);
202 		free(w->events);
203 		free(w);
204 	}
205 }
206 
207 void
208 windelete(Window *w)
209 {
210 	Xfid *x;
211 
212 	x = w->eventx;
213 	if(x){
214 		w->nevents = 0;
215 		free(w->events);
216 		w->events = nil;
217 		w->eventx = nil;
218 		sendp(x->c, nil);	/* wake him up */
219 	}
220 }
221 
222 void
223 winundo(Window *w, int isundo)
224 {
225 	Text *body;
226 	int i;
227 	File *f;
228 	Window *v;
229 
230 	w->utflastqid = -1;
231 	body = &w->body;
232 	fileundo(body->file, isundo, &body->q0, &body->q1);
233 	textshow(body, body->q0, body->q1, 1);
234 	f = body->file;
235 	for(i=0; i<f->ntext; i++){
236 		v = f->text[i]->w;
237 		v->dirty = (f->seq != v->putseq);
238 		if(v != w){
239 			v->body.q0 = v->body.p0+v->body.org;
240 			v->body.q1 = v->body.p1+v->body.org;
241 		}
242 	}
243 	winsettag(w);
244 }
245 
246 void
247 winsetname(Window *w, Rune *name, int n)
248 {
249 	Text *t;
250 	Window *v;
251 	int i;
252 
253 	t = &w->body;
254 	if(runeeq(t->file->name, t->file->nname, name, n) == TRUE)
255 		return;
256 	w->isscratch = FALSE;
257 	if(n>=6 && runeeq(L"/guide", 6, name+(n-6), 6))
258 		w->isscratch = TRUE;
259 	else if(n>=7 && runeeq(L"+Errors", 7, name+(n-7), 7))
260 		w->isscratch = TRUE;
261 	filesetname(t->file, name, n);
262 	for(i=0; i<t->file->ntext; i++){
263 		v = t->file->text[i]->w;
264 		winsettag(v);
265 		v->isscratch = w->isscratch;
266 	}
267 }
268 
269 void
270 wintype(Window *w, Text *t, Rune r)
271 {
272 	int i;
273 
274 	texttype(t, r);
275 	if(t->what == Body)
276 		for(i=0; i<t->file->ntext; i++)
277 			textscrdraw(t->file->text[i]);
278 	winsettag(w);
279 }
280 
281 void
282 wincleartag(Window *w)
283 {
284 	int i, n;
285 	Rune *r;
286 
287 	/* w must be committed */
288 	n = w->tag.file->nc;
289 	r = runemalloc(n);
290 	bufread(w->tag.file, 0, r, n);
291 	for(i=0; i<n; i++)
292 		if(r[i]==' ' || r[i]=='\t')
293 			break;
294 	for(; i<n; i++)
295 		if(r[i] == '|')
296 			break;
297 	if(i == n)
298 		return;
299 	i++;
300 	textdelete(&w->tag, i, n, TRUE);
301 	free(r);
302 	w->tag.file->mod = FALSE;
303 	if(w->tag.q0 > i)
304 		w->tag.q0 = i;
305 	if(w->tag.q1 > i)
306 		w->tag.q1 = i;
307 	textsetselect(&w->tag, w->tag.q0, w->tag.q1);
308 }
309 
310 void
311 winsettag1(Window *w)
312 {
313 	int i, j, k, n, bar, dirty;
314 	Rune *new, *old, *r;
315 	Image *b;
316 	uint q0, q1;
317 	Rectangle br;
318 
319 	/* there are races that get us here with stuff in the tag cache, so we take extra care to sync it */
320 	if(w->tag.ncache!=0 || w->tag.file->mod)
321 		wincommit(w, &w->tag);	/* check file name; also guarantees we can modify tag contents */
322 	old = runemalloc(w->tag.file->nc+1);
323 	bufread(w->tag.file, 0, old, w->tag.file->nc);
324 	old[w->tag.file->nc] = '\0';
325 	for(i=0; i<w->tag.file->nc; i++)
326 		if(old[i]==' ' || old[i]=='\t')
327 			break;
328 	if(runeeq(old, i, w->body.file->name, w->body.file->nname) == FALSE){
329 		textdelete(&w->tag, 0, i, TRUE);
330 		textinsert(&w->tag, 0, w->body.file->name, w->body.file->nname, TRUE);
331 		free(old);
332 		old = runemalloc(w->tag.file->nc+1);
333 		bufread(w->tag.file, 0, old, w->tag.file->nc);
334 		old[w->tag.file->nc] = '\0';
335 	}
336 	new = runemalloc(w->body.file->nname+100);
337 	i = 0;
338 	runemove(new+i, w->body.file->name, w->body.file->nname);
339 	i += w->body.file->nname;
340 	runemove(new+i, L" Del Snarf", 10);
341 	i += 10;
342 	if(w->filemenu){
343 		if(w->body.file->delta.nc>0 || w->body.ncache){
344 			runemove(new+i, L" Undo", 5);
345 			i += 5;
346 		}
347 		if(w->body.file->epsilon.nc > 0){
348 			runemove(new+i, L" Redo", 5);
349 			i += 5;
350 		}
351 		dirty = w->body.file->nname && (w->body.ncache || w->body.file->seq!=w->putseq);
352 		if(!w->isdir && dirty){
353 			runemove(new+i, L" Put", 4);
354 			i += 4;
355 		}
356 	}
357 	if(w->isdir){
358 		runemove(new+i, L" Get", 4);
359 		i += 4;
360 	}
361 	runemove(new+i, L" |", 2);
362 	i += 2;
363 	r = runestrchr(old, '|');
364 	if(r)
365 		k = r-old+1;
366 	else{
367 		k = w->tag.file->nc;
368 		if(w->body.file->seq == 0){
369 			runemove(new+i, L" Look ", 6);
370 			i += 6;
371 		}
372 	}
373 	if(runeeq(new, i, old, k) == FALSE){
374 		n = k;
375 		if(n > i)
376 			n = i;
377 		for(j=0; j<n; j++)
378 			if(old[j] != new[j])
379 				break;
380 		q0 = w->tag.q0;
381 		q1 = w->tag.q1;
382 		textdelete(&w->tag, j, k, TRUE);
383 		textinsert(&w->tag, j, new+j, i-j, TRUE);
384 		/* try to preserve user selection */
385 		r = runestrchr(old, '|');
386 		if(r){
387 			bar = r-old;
388 			if(q0 > bar){
389 				bar = (runestrchr(new, '|')-new)-bar;
390 				w->tag.q0 = q0+bar;
391 				w->tag.q1 = q1+bar;
392 			}
393 		}
394 	}
395 	free(old);
396 	free(new);
397 	w->tag.file->mod = FALSE;
398 	n = w->tag.file->nc+w->tag.ncache;
399 	if(w->tag.q0 > n)
400 		w->tag.q0 = n;
401 	if(w->tag.q1 > n)
402 		w->tag.q1 = n;
403 	textsetselect(&w->tag, w->tag.q0, w->tag.q1);
404 	b = button;
405 	if(!w->isdir && !w->isscratch && (w->body.file->mod || w->body.ncache))
406 		b = modbutton;
407 	br.min = w->tag.scrollr.min;
408 	br.max.x = br.min.x + Dx(b->r);
409 	br.max.y = br.min.y + Dy(b->r);
410 	draw(screen, br, b, nil, b->r.min);
411 }
412 
413 void
414 winsettag(Window *w)
415 {
416 	int i;
417 	File *f;
418 	Window *v;
419 
420 	f = w->body.file;
421 	for(i=0; i<f->ntext; i++){
422 		v = f->text[i]->w;
423 		if(v->col->safe || v->body.maxlines>0)
424 			winsettag1(v);
425 	}
426 }
427 
428 void
429 wincommit(Window *w, Text *t)
430 {
431 	Rune *r;
432 	int i;
433 	File *f;
434 
435 	textcommit(t, TRUE);
436 	f = t->file;
437 	if(f->ntext > 1)
438 		for(i=0; i<f->ntext; i++)
439 			textcommit(f->text[i], FALSE);	/* no-op for t */
440 	if(t->what == Body)
441 		return;
442 	r = runemalloc(w->tag.file->nc);
443 	bufread(w->tag.file, 0, r, w->tag.file->nc);
444 	for(i=0; i<w->tag.file->nc; i++)
445 		if(r[i]==' ' || r[i]=='\t')
446 			break;
447 	if(runeeq(r, i, w->body.file->name, w->body.file->nname) == FALSE){
448 		seq++;
449 		filemark(w->body.file);
450 		w->body.file->mod = TRUE;
451 		w->dirty = TRUE;
452 		winsetname(w, r, i);
453 		winsettag(w);
454 	}
455 	free(r);
456 }
457 
458 void
459 winaddincl(Window *w, Rune *r, int n)
460 {
461 	char *a;
462 	Dir *d;
463 	Runestr rs;
464 
465 	a = runetobyte(r, n);
466 	d = dirstat(a);
467 	if(d == nil){
468 		if(a[0] == '/')
469 			goto Rescue;
470 		rs = dirname(&w->body, r, n);
471 		r = rs.r;
472 		n = rs.nr;
473 		free(a);
474 		a = runetobyte(r, n);
475 		d = dirstat(a);
476 		if(d == nil)
477 			goto Rescue;
478 		r = runerealloc(r, n+1);
479 		r[n] = 0;
480 	}
481 	free(a);
482 	if((d->qid.type&QTDIR) == 0){
483 		free(d);
484 		warning(nil, "%s: not a directory\n", a);
485 		free(r);
486 		return;
487 	}
488 	free(d);
489 	w->nincl++;
490 	w->incl = realloc(w->incl, w->nincl*sizeof(Rune*));
491 	memmove(w->incl+1, w->incl, (w->nincl-1)*sizeof(Rune*));
492 	w->incl[0] = runemalloc(n+1);
493 	runemove(w->incl[0], r, n);
494 	free(r);
495 	return;
496 
497 Rescue:
498 	warning(nil, "%s: %r\n", a);
499 	free(r);
500 	free(a);
501 	return;
502 }
503 
504 int
505 winclean(Window *w, int conservative)	/* as it stands, conservative is always TRUE */
506 {
507 	if(w->isscratch || w->isdir)	/* don't whine if it's a guide file, error window, etc. */
508 		return TRUE;
509 	if(!conservative && w->nopen[QWevent]>0)
510 		return TRUE;
511 	if(w->dirty){
512 		if(w->body.file->nname)
513 			warning(nil, "%.*S modified\n", w->body.file->nname, w->body.file->name);
514 		else{
515 			if(w->body.file->nc < 100)	/* don't whine if it's too small */
516 				return TRUE;
517 			warning(nil, "unnamed file modified\n");
518 		}
519 		w->dirty = FALSE;
520 		return FALSE;
521 	}
522 	return TRUE;
523 }
524 
525 void
526 winctlprint(Window *w, char *buf, int fonts)
527 {
528 	int n;
529 
530 	n = sprint(buf, "%11d %11d %11d %11d %11d ", w->id, w->tag.file->nc,
531 		w->body.file->nc, w->isdir, w->dirty);
532 	if(fonts)
533 		sprint(buf+n, "%11d %s" , Dx(w->body.r), w->body.reffont->f->name);
534 }
535 
536 void
537 winevent(Window *w, char *fmt, ...)
538 {
539 	int n;
540 	char *b;
541 	Xfid *x;
542 	va_list arg;
543 
544 	if(w->nopen[QWevent] == 0)
545 		return;
546 	if(w->owner == 0)
547 		error("no window owner");
548 	va_start(arg, fmt);
549 	b = vsmprint(fmt, arg);
550 	va_end(arg);
551 	if(b == nil)
552 		error("vsmprint failed");
553 	n = strlen(b);
554 	w->events = realloc(w->events, w->nevents+1+n);
555 	w->events[w->nevents++] = w->owner;
556 	memmove(w->events+w->nevents, b, n);
557 	free(b);
558 	w->nevents += n;
559 	x = w->eventx;
560 	if(x){
561 		w->eventx = nil;
562 		sendp(x->c, nil);
563 	}
564 }
565