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