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