xref: /plan9/sys/src/cmd/acme/wind.c (revision 58da3067adcdccaaa043d0bfde28ba83b7ced07d)
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 	/*
157 	 * subtle: loop runs backwards to avoid tripping over
158 	 * winclose indirectly editing f->text and freeing f
159 	 * on the last iteration of the loop.
160 	 */
161 	f = w->body.file;
162 	for(i=f->ntext-1; i>=0; i--){
163 		w = f->text[i]->w;
164 		w->owner = 0;
165 		qunlock(w);
166 		winclose(w);
167 	}
168 }
169 
170 void
171 winmousebut(Window *w)
172 {
173 	moveto(mousectl, divpt(addpt(w->tag.scrollr.min, w->tag.scrollr.max), 2));
174 }
175 
176 void
177 windirfree(Window *w)
178 {
179 	int i;
180 	Dirlist *dl;
181 
182 	if(w->isdir){
183 		for(i=0; i<w->ndl; i++){
184 			dl = w->dlp[i];
185 			free(dl->r);
186 			free(dl);
187 		}
188 		free(w->dlp);
189 	}
190 	w->dlp = nil;
191 	w->ndl = 0;
192 }
193 
194 void
195 winclose(Window *w)
196 {
197 	int i;
198 
199 	if(decref(w) == 0){
200 		windirfree(w);
201 		textclose(&w->tag);
202 		textclose(&w->body);
203 		if(activewin == w)
204 			activewin = nil;
205 		for(i=0; i<w->nincl; i++)
206 			free(w->incl[i]);
207 		free(w->incl);
208 		free(w->events);
209 		free(w);
210 	}
211 }
212 
213 void
214 windelete(Window *w)
215 {
216 	Xfid *x;
217 
218 	x = w->eventx;
219 	if(x){
220 		w->nevents = 0;
221 		free(w->events);
222 		w->events = nil;
223 		w->eventx = nil;
224 		sendp(x->c, nil);	/* wake him up */
225 	}
226 }
227 
228 void
229 winundo(Window *w, int isundo)
230 {
231 	Text *body;
232 	int i;
233 	File *f;
234 	Window *v;
235 
236 	w->utflastqid = -1;
237 	body = &w->body;
238 	fileundo(body->file, isundo, &body->q0, &body->q1);
239 	textshow(body, body->q0, body->q1, 1);
240 	f = body->file;
241 	for(i=0; i<f->ntext; i++){
242 		v = f->text[i]->w;
243 		v->dirty = (f->seq != v->putseq);
244 		if(v != w){
245 			v->body.q0 = v->body.p0+v->body.org;
246 			v->body.q1 = v->body.p1+v->body.org;
247 		}
248 	}
249 	winsettag(w);
250 }
251 
252 void
253 winsetname(Window *w, Rune *name, int n)
254 {
255 	Text *t;
256 	Window *v;
257 	int i;
258 
259 	t = &w->body;
260 	if(runeeq(t->file->name, t->file->nname, name, n) == TRUE)
261 		return;
262 	w->isscratch = FALSE;
263 	if(n>=6 && runeeq(L"/guide", 6, name+(n-6), 6))
264 		w->isscratch = TRUE;
265 	else if(n>=7 && runeeq(L"+Errors", 7, name+(n-7), 7))
266 		w->isscratch = TRUE;
267 	filesetname(t->file, name, n);
268 	for(i=0; i<t->file->ntext; i++){
269 		v = t->file->text[i]->w;
270 		winsettag(v);
271 		v->isscratch = w->isscratch;
272 	}
273 }
274 
275 void
276 wintype(Window *w, Text *t, Rune r)
277 {
278 	int i;
279 
280 	texttype(t, r);
281 	if(t->what == Body)
282 		for(i=0; i<t->file->ntext; i++)
283 			textscrdraw(t->file->text[i]);
284 	winsettag(w);
285 }
286 
287 void
288 wincleartag(Window *w)
289 {
290 	int i, n;
291 	Rune *r;
292 
293 	/* w must be committed */
294 	n = w->tag.file->nc;
295 	r = runemalloc(n);
296 	bufread(w->tag.file, 0, r, n);
297 	for(i=0; i<n; i++)
298 		if(r[i]==' ' || r[i]=='\t')
299 			break;
300 	for(; i<n; i++)
301 		if(r[i] == '|')
302 			break;
303 	if(i == n)
304 		return;
305 	i++;
306 	textdelete(&w->tag, i, n, TRUE);
307 	free(r);
308 	w->tag.file->mod = FALSE;
309 	if(w->tag.q0 > i)
310 		w->tag.q0 = i;
311 	if(w->tag.q1 > i)
312 		w->tag.q1 = i;
313 	textsetselect(&w->tag, w->tag.q0, w->tag.q1);
314 }
315 
316 void
317 winsettag1(Window *w)
318 {
319 	int i, j, k, n, bar, dirty;
320 	Rune *new, *old, *r;
321 	Image *b;
322 	uint q0, q1;
323 	Rectangle br;
324 
325 	/* there are races that get us here with stuff in the tag cache, so we take extra care to sync it */
326 	if(w->tag.ncache!=0 || w->tag.file->mod)
327 		wincommit(w, &w->tag);	/* check file name; also guarantees we can modify tag contents */
328 	old = runemalloc(w->tag.file->nc+1);
329 	bufread(w->tag.file, 0, old, w->tag.file->nc);
330 	old[w->tag.file->nc] = '\0';
331 	for(i=0; i<w->tag.file->nc; i++)
332 		if(old[i]==' ' || old[i]=='\t')
333 			break;
334 	if(runeeq(old, i, w->body.file->name, w->body.file->nname) == FALSE){
335 		textdelete(&w->tag, 0, i, TRUE);
336 		textinsert(&w->tag, 0, w->body.file->name, w->body.file->nname, TRUE);
337 		free(old);
338 		old = runemalloc(w->tag.file->nc+1);
339 		bufread(w->tag.file, 0, old, w->tag.file->nc);
340 		old[w->tag.file->nc] = '\0';
341 	}
342 	new = runemalloc(w->body.file->nname+100);
343 	i = 0;
344 	runemove(new+i, w->body.file->name, w->body.file->nname);
345 	i += w->body.file->nname;
346 	runemove(new+i, L" Del Snarf", 10);
347 	i += 10;
348 	if(w->filemenu){
349 		if(w->body.file->delta.nc>0 || w->body.ncache){
350 			runemove(new+i, L" Undo", 5);
351 			i += 5;
352 		}
353 		if(w->body.file->epsilon.nc > 0){
354 			runemove(new+i, L" Redo", 5);
355 			i += 5;
356 		}
357 		dirty = w->body.file->nname && (w->body.ncache || w->body.file->seq!=w->putseq);
358 		if(!w->isdir && dirty){
359 			runemove(new+i, L" Put", 4);
360 			i += 4;
361 		}
362 	}
363 	if(w->isdir){
364 		runemove(new+i, L" Get", 4);
365 		i += 4;
366 	}
367 	runemove(new+i, L" |", 2);
368 	i += 2;
369 	r = runestrchr(old, '|');
370 	if(r)
371 		k = r-old+1;
372 	else{
373 		k = w->tag.file->nc;
374 		if(w->body.file->seq == 0){
375 			runemove(new+i, L" Look ", 6);
376 			i += 6;
377 		}
378 	}
379 	if(runeeq(new, i, old, k) == FALSE){
380 		n = k;
381 		if(n > i)
382 			n = i;
383 		for(j=0; j<n; j++)
384 			if(old[j] != new[j])
385 				break;
386 		q0 = w->tag.q0;
387 		q1 = w->tag.q1;
388 		textdelete(&w->tag, j, k, TRUE);
389 		textinsert(&w->tag, j, new+j, i-j, TRUE);
390 		/* try to preserve user selection */
391 		r = runestrchr(old, '|');
392 		if(r){
393 			bar = r-old;
394 			if(q0 > bar){
395 				bar = (runestrchr(new, '|')-new)-bar;
396 				w->tag.q0 = q0+bar;
397 				w->tag.q1 = q1+bar;
398 			}
399 		}
400 	}
401 	free(old);
402 	free(new);
403 	w->tag.file->mod = FALSE;
404 	n = w->tag.file->nc+w->tag.ncache;
405 	if(w->tag.q0 > n)
406 		w->tag.q0 = n;
407 	if(w->tag.q1 > n)
408 		w->tag.q1 = n;
409 	textsetselect(&w->tag, w->tag.q0, w->tag.q1);
410 	b = button;
411 	if(!w->isdir && !w->isscratch && (w->body.file->mod || w->body.ncache))
412 		b = modbutton;
413 	br.min = w->tag.scrollr.min;
414 	br.max.x = br.min.x + Dx(b->r);
415 	br.max.y = br.min.y + Dy(b->r);
416 	draw(screen, br, b, nil, b->r.min);
417 }
418 
419 void
420 winsettag(Window *w)
421 {
422 	int i;
423 	File *f;
424 	Window *v;
425 
426 	f = w->body.file;
427 	for(i=0; i<f->ntext; i++){
428 		v = f->text[i]->w;
429 		if(v->col->safe || v->body.maxlines>0)
430 			winsettag1(v);
431 	}
432 }
433 
434 void
435 wincommit(Window *w, Text *t)
436 {
437 	Rune *r;
438 	int i;
439 	File *f;
440 
441 	textcommit(t, TRUE);
442 	f = t->file;
443 	if(f->ntext > 1)
444 		for(i=0; i<f->ntext; i++)
445 			textcommit(f->text[i], FALSE);	/* no-op for t */
446 	if(t->what == Body)
447 		return;
448 	r = runemalloc(w->tag.file->nc);
449 	bufread(w->tag.file, 0, r, w->tag.file->nc);
450 	for(i=0; i<w->tag.file->nc; i++)
451 		if(r[i]==' ' || r[i]=='\t')
452 			break;
453 	if(runeeq(r, i, w->body.file->name, w->body.file->nname) == FALSE){
454 		seq++;
455 		filemark(w->body.file);
456 		w->body.file->mod = TRUE;
457 		w->dirty = TRUE;
458 		winsetname(w, r, i);
459 		winsettag(w);
460 	}
461 	free(r);
462 }
463 
464 void
465 winaddincl(Window *w, Rune *r, int n)
466 {
467 	char *a;
468 	Dir *d;
469 	Runestr rs;
470 
471 	a = runetobyte(r, n);
472 	d = dirstat(a);
473 	if(d == nil){
474 		if(a[0] == '/')
475 			goto Rescue;
476 		rs = dirname(&w->body, r, n);
477 		r = rs.r;
478 		n = rs.nr;
479 		free(a);
480 		a = runetobyte(r, n);
481 		d = dirstat(a);
482 		if(d == nil)
483 			goto Rescue;
484 		r = runerealloc(r, n+1);
485 		r[n] = 0;
486 	}
487 	free(a);
488 	if((d->qid.type&QTDIR) == 0){
489 		free(d);
490 		warning(nil, "%s: not a directory\n", a);
491 		free(r);
492 		return;
493 	}
494 	free(d);
495 	w->nincl++;
496 	w->incl = realloc(w->incl, w->nincl*sizeof(Rune*));
497 	memmove(w->incl+1, w->incl, (w->nincl-1)*sizeof(Rune*));
498 	w->incl[0] = runemalloc(n+1);
499 	runemove(w->incl[0], r, n);
500 	free(r);
501 	return;
502 
503 Rescue:
504 	warning(nil, "%s: %r\n", a);
505 	free(r);
506 	free(a);
507 	return;
508 }
509 
510 int
511 winclean(Window *w, int conservative)	/* as it stands, conservative is always TRUE */
512 {
513 	if(w->isscratch || w->isdir)	/* don't whine if it's a guide file, error window, etc. */
514 		return TRUE;
515 	if(!conservative && w->nopen[QWevent]>0)
516 		return TRUE;
517 	if(w->dirty){
518 		if(w->body.file->nname)
519 			warning(nil, "%.*S modified\n", w->body.file->nname, w->body.file->name);
520 		else{
521 			if(w->body.file->nc < 100)	/* don't whine if it's too small */
522 				return TRUE;
523 			warning(nil, "unnamed file modified\n");
524 		}
525 		w->dirty = FALSE;
526 		return FALSE;
527 	}
528 	return TRUE;
529 }
530 
531 char*
532 winctlprint(Window *w, char *buf, int fonts)
533 {
534 	sprint(buf, "%11d %11d %11d %11d %11d ", w->id, w->tag.file->nc,
535 		w->body.file->nc, w->isdir, w->dirty);
536 	if(fonts)
537 		return smprint("%s%11d %q %11d " , buf, Dx(w->body.r),
538 			w->body.reffont->f->name, w->body.maxtab);
539 	return buf;
540 }
541 
542 void
543 winevent(Window *w, char *fmt, ...)
544 {
545 	int n;
546 	char *b;
547 	Xfid *x;
548 	va_list arg;
549 
550 	if(w->nopen[QWevent] == 0)
551 		return;
552 	if(w->owner == 0)
553 		error("no window owner");
554 	va_start(arg, fmt);
555 	b = vsmprint(fmt, arg);
556 	va_end(arg);
557 	if(b == nil)
558 		error("vsmprint failed");
559 	n = strlen(b);
560 	w->events = realloc(w->events, w->nevents+1+n);
561 	w->events[w->nevents++] = w->owner;
562 	memmove(w->events+w->nevents, b, n);
563 	free(b);
564 	w->nevents += n;
565 	x = w->eventx;
566 	if(x){
567 		w->eventx = nil;
568 		sendp(x->c, nil);
569 	}
570 }
571