#include #include float w = 0; persist(w); float h = 0; persist(h); persist(mx); matrixtype mx() { return matrix(); } float x() { return mx[4]; } float y() { return mx[5]; } /******************************************************************** * Member management */ void addmember(drawable m) { m.visible = false; super.addmember(m); invalidate(); } void removemember(drawable m) { m.onclose(); super.removemember(m); invalidate(); } /******************************************************************** * Graphics state */ graphicsstatetype gs = null; void gstart() { } void gstop() { unpromote(/gs); } void greset() { drawable m; forall(; m ; members) m.greset(); gstop(); } void gset() { concat(mx); path(); clip(); newpath(); } void ginit() { if (visible) { if (gs != null) setstate(gs); else { @parent.ginit(); gset(); gs = currentstate(); } } else { @parent.ginit(); gset(); } } void gexec(name proc) { if (visible && validated) { gsave(); ginit(); EXEC(proc); grestore(); } } void gexeca(name proc, any *args) { if (visible && validated) { gsave(); ginit(); EXEC(proc,args); grestore(); } } void gfix(name proc) { if (visible && validated) { gsave(); ginit(); clipcanvaspath(); clip(); clippath(); if (!emptypath()) { newpath(); EXEC(proc); } grestore(); } } /******************************************************************** * Slave driving */ drawable *slaves = (drawable *)[]; void addslave(drawable s) { s.@owner = (drawable)soften(self); slaves = (drawable *)append(slaves,[s]); initmember(s); if (visible) s.owner_open(); } void removeslave(drawable s) { int i; if ((i = arrayindex(slaves,s)) < 0) return; if (visible) s.owner_close(); s.@owner = null; slaves = (drawable *)arraydelete(slaves,i); return; } void delslave(drawable s) { removeslave(s); s.destroy(); } void owner_open() { map(); } void owner_close() { unmap(); } void destroy() { drawable s; if (@owner) @owner.delslave(self); forall(; s ; slaves) { s.@owner = null; s.destroy(); } super.destroy(); } /******************************************************************** * Mapping */ boolean mapped() { return !promoted(/visible); } void map() { if (!mapped()) { unpromote(/visible); onopen(); damage(); } } void unmap() { if (mapped()) { damage(); onclose(); visible = false; } } void OnOpen() { } void OnClose() { } void onopen() { drawable m; if (visible) { validate(); gstart(); QSend(OnOpen); forall (; m ; members) m.onopen(); forall (; m ; slaves) m.owner_open(); } } void onclose() { drawable m; if (visible) { forall (; m ; slaves) m.owner_close(); forall (; m ; members) m.onclose(); QSend(OnClose); gstop(); } } boolean setscript(char *pdb, char *ps) { boolean ok; onclose(); ok = super.setscript(pdb,ps); onopen(); return ok; } /******************************************************************** * Painting, fixing and damage */ void Paint() { path(); setcolor(BG); fill(); } void path() { rectpath(0,0,w,h); } void paintall() { drawable m; Send Paint(); forall (; m ; members) m.paint(); } void paint() { gexec(/paintall); } void fixall() { drawable m; Send Paint(); forall (; m ; members) m.fix(); } void fix() { gfix(/fixall); } void damageall() { clippath(); extenddamage(); } void damage() { gexec(/damageall); } /******************************************************************** * Move and resize */ boolean fixedwidth = false; boolean fixedheight = false; float minwidth = 0; float minheight = 0; float preferredwidth = 0; float preferredheight = 0; void preferredshape() { reshape(x,y,w,h); } rectangle bounds() { return [x,y,w,h]; } void move(float X, float Y) { drawable p; if ((x != X) || (y != Y)) { damage(); softpromote(/mx,mtranslate(mx,X,Y)); if (p = @parent) p.invalidate(); greset(); damage(); } } void rmove(float X, float Y) { move(x + X, y + Y); } void reshape(float X, float Y, float W, float H) { drawable p; W = max(minwidth,(fixedwidth ? preferredwidth : W)); H = max(minheight,(fixedheight ? preferredheight : H)); if ((w == W) && (h == H)) move(X,Y); else { damage(); softpromote(/h,H); softpromote(/w,W); softpromote(/mx,mtranslate(mx,X,Y)); greset(); damage(); if (p = @parent) p.invalidate(); invalidate(); } } /******************************************************************** * validation */ boolean invalid = false; void Validate() { } void validate() { if (invalid && visible) { placement(); QSend(Validate); damage(); } } void invalidate() { if (!invalid) { invalid = true; if (visible) validate(); } } /******************************************************************** * Positioning */ #define POSMAX 10000 persist(justification); float posE = POSMAX; persist(posE); float posW = POSMAX; persist(posW); float posN = POSMAX; persist(posN); float posS = POSMAX; persist(posS); void Position(float X, float Y, float W, float H) { reshape(X,Y,W,H); } void position(float PW, float PH) { float X = x, Y = y, W, H; W = max(minwidth,(fixedwidth ? preferredwidth : w)); H = max(minheight,(fixedheight ? preferredheight : h)); if (posW < POSMAX) { X = posW; if (posE < POSMAX) { if (fixedwidth) X = (PW-W)/2; else W = PW - (X + posE); } } else if (posE < POSMAX) X = PW - (W + posE); if (posS < POSMAX) { Y = posS; if (posN < POSMAX) { if (fixedheight) Y = (PH-H)/2; else H = PH - (Y + posN); } } else if (posN < POSMAX) Y = PH - (H + posN); /*DEBUGF("POS % % % % %",[namestr,X,Y,W,H]);*/ Send Position(X,Y,W,H); } void setposition(any W, any S, any E, any N) { drawable p; W = (W != null) ? (float)W : POSMAX; S = (S != null) ? (float)S : POSMAX; E = (E != null) ? (float)E : POSMAX; N = (N != null) ? (float)N : POSMAX; if ((W != posW) || (S != posS) || (E != posE) || (N != posN)) { softpromote(/posW,W); softpromote(/posS,S); softpromote(/posE,E); softpromote(/posN,N); if (p = @parent) p.invalidate(); } } /******************************************************************** * Placement */ name placemode = /position; persist(placemode); float place_h = 10; persist(place_h); float place_v = 10; persist(place_v); void Placement(name mode) { drawable m; forall ( ; m ; members) m.position(w,h); } void placement_left2right() { float cw, ch, cx, cy; int f, t, nr = length(members); drawable m; forall(; m ; members) m.preferredshape(); for (cy = h, f = 0 ; f < nr ; cy -= place_v) { cw = w - members[f].w(); ch = members[f].h(); for (t = f+1 ; (cw > 0) && (t < nr) ; ) { if ((members[t].w() + place_h) > cw) break; cw -= members[t].w() + place_h; ch = max(ch,members[t++].h()); } switch (justification) { case /Centered: cx = cw/2; break; case /Right: cx = cw; break; default: cx = 0; } for (cy -= ch ; f < t ; f++) { members[f].move(cx,cy); cx += members[f].w() + place_h; } } } void placement_top2bottom() { float cw, ch, cx, cy; int f, t, nr = length(members); drawable m; forall(; m ; members) m.preferredshape(); for (cx = 0, f = 0 ; f < nr ; cx += place_h + cw) { ch = h - members[f].h(); cw = members[f].w(); for (t = f+1 ; (ch > 0) && (t < nr) ; ) { if ((members[t].h() + place_v) > ch) break; ch -= members[t].h() + place_v; cw = max(cw,members[t++].w()); } for (cy = h ; f < t ; f++, cy -= place_v) { cy -= members[f].h(); switch (justification) { case /Centered: members[f].move(cx + (cw - members[f].w())/2,cy); break; case /Right: members[f].move(cx + (cw - members[f].w()),cy); break; default: members[f].move(cx,cy); } } } } void placement() { drawable m; validated = false; switch (placemode) { case /left2right: placement_left2right(); break; case /top2bottom: placement_top2bottom(); break; case /none: break; case /position: forall ( ; m ; members) m.position(w,h); break; default: Send Placement(placemode); break; } unpromote(/validated); unpromote(/invalid); } void setplacement(name mode) { if (mode != placemode) { softpromote(/placemode,mode); invalidate(); } } void setjustification(name justify) { if (justify != justification) { softpromote(/justification,justify); invalidate(); } } /******************************************************************** * Menu handling */ void OnMenu() { if (@menu) ((struct menu)@menu).showmenu(self); } void onmenu() { if (visible && @menu) gexec(/OnMenu); else @parent.onmenu(); } /******************************************************************** * mouse */ void OnSelect() { } void OnAdjust() { } void onselect() { gexec(/OnSelect); } void onadjust() { gexec(/OnAdjust); } /******************************************************************** * hit detection */ boolean inpath(eventtype e) { boolean r; gsave(); ginit(); path(); r = pointinpath(e.XLocation,e.YLocation); grestore(); return r; } drawable hit(eventtype e) { drawable m; int i; if (visible && inpath(e)) { for (i = length(members)-1 ; i >= 0 ; i--) if (m = members[i].hit(e)) return m; return self; } return null; } drawable *hitlist(eventtype e) { drawable *s = (drawable *)[], m; forall(; m ; members) if (m.inpath(e)) s = (drawable *)append(s,[m]); return s; } drawable *recthitlist(rectangle r) { drawable *s = (drawable *)[], m; forall(; m ; members) if (rectinrect(r,m.bounds())) s = (drawable *)append(s,[m]); return s; } /******************************************************************** * focus management */ nametype focus_state() { if (self == @focus) { if (cv == currentinputfocus) return /active; else return /inactive; } else return /none; } void request_focus() { if (self != @focus) Send set_focus(self) to @window; } void focus_change() { } /******************************************************************** * editing */ drawable *edit_sel = (drawable *)[]; int grid = 5; #define align2grid(x) ((x) - ((x) % grid)) boolean edit_mode() { return known(@window,/edit_mode) ? @window.edit_mode : false; } boolean edit_ctx_dragable() { return @edit_ctx.placemode() == /position; } drawable @edit_ctx() { if (known(@current_layout,/edit_ctx)) if (@current_layout[/edit_ctx] != null) return (drawable)@current_layout[/edit_ctx]; else return @window_layout; else return @current_layout; } void edit_rmove(float X, float Y) { if (posW < POSMAX) posW += X; if (posE < POSMAX) posE -= X; if (posS < POSMAX) posS += Y; if (posN < POSMAX) posN -= Y; rmove(X,Y); } void edit_reshape(float X, float Y, float W, float H) { W = max(W,minwidth); H = max(H,minheight); if (fixedwidth && preferredwidth) W = preferredwidth; if (fixedheight && preferredheight) H = preferredheight; if (posW < POSMAX) posW += X - x; if (posE < POSMAX) posE -= (X+W) - (x + w); if (posS < POSMAX) posS += Y - y; if (posN < POSMAX) posN -= (Y+H) - (y + h); reshape(X,Y,W,H); } /********************************************************************* * Paint edit context and edit selection */ void xor_mode(colortype c) { matrixtype m = currentmatrix(matrix); setcanvas(cv); setmatrix(m); setxorop(BG,c); } #define parent_edit_sel (known(@parent,/edit_sel) ? @parent.edit_sel : edit_sel) #define reshape_handle(X,Y) rectpath((X)-2,(Y)-2,5,5) void paint_selected() { gsave(); concat(mx); xor_mode(editSELcolor); gsave(); rectpath(insetstroke(-1,[0,0,w,h])); if (!edit_ctx_dragable()) setdash([6,6],0); stroke(); grestore(); if (length(parent_edit_sel) <= 1) { if (!fixedwidth) { reshape_handle(-3,h/2); reshape_handle(w+2,h/2); } if (!fixedheight) { reshape_handle(w/2,-3); reshape_handle(w/2,h+2); } if ((!fixedwidth) && (!fixedheight)) { reshape_handle(-3,-3); reshape_handle(w+2,-3); reshape_handle(w+2,h+2); reshape_handle(-3,h+2); } fill(); } grestore(); } void paint_edit_sel() { drawable m; forall (; m ; edit_sel) m.paint_selected(); } void paint_edit_mode() { xor_mode(editCTXcolor); rectpath(insetstroke(-1,[0,0,w,h])); rectpath(insetstroke(-2,[0,0,w,h])); stroke(); paint_edit_sel(); } /********************************************************************* * change context/selection */ void set_edit_sel(drawable *s) { gexec(/paint_edit_sel); edit_sel = s; gexec(/paint_edit_sel); } void start_edit_ctx() { map(); gexec(/paint_edit_mode); } void stop_edit_ctx() { gexec(/paint_edit_mode); unpromote(/edit_sel); } void edit_ctx_parent() { Send set_edit_ctx(@parent) to @window; Send set_edit_sel([self]) to @edit_ctx; } void edit_ctx_selected() { if (length(edit_sel) == 1) Send set_edit_ctx(edit_sel[0]) to @window; } /********************************************************************* * Select from a rubber rect */ void edit_rubber_rect_track() { stroke(); rectpath(points2rect(mouseevent.XLocation,mouseevent.YLocation,trackx,tracky)); gsave(); stroke(); grestore(); } rectangle edit_rubber_rect() { xor_mode(editSELcolor); rectpath(mouseevent.XLocation,mouseevent.YLocation,0,0); gsave(); stroke(); grestore(); track(/edit_rubber_rect_track); stroke(); return points2rect(mouseevent.XLocation,mouseevent.YLocation,trackx,tracky); } void edit_select_from_rect() { set_edit_sel((drawable *)[]); set_edit_sel(recthitlist(edit_rubber_rect())); } /********************************************************************* * Drag selection */ float dragx = 0, dragy = 0; void edit_drag_sel_track() { float X = align2grid(trackdx), Y = align2grid(trackdy); if ((dragx != X) || (dragy != Y)) { paint_edit_sel(); grestore(); gsave(); translate(dragx = X,dragy = Y); paint_edit_sel(); } } void edit_drag_sel() { drawable m; if (edit_ctx_dragable()) { gsave(); track(/edit_drag_sel_track); grestore(); forall (; m ; edit_sel) m.edit_rmove(dragx,dragy); unpromote(/dragx); unpromote(/dragy); } } /********************************************************************* * Edit reshape handles */ #define NEAR(a,b) (abs((a) - (b)) < 5) name which_reshape_handle(float ex, float ey) { if (NEAR(ex,w+2) && (!fixedwidth)) { if (NEAR(ey,h/2)) return /West; if (!fixedheight) { if (NEAR(ey,-3)) return /SouthWest; if (NEAR(ey,h+2)) return /NorthWest; } } else if (NEAR(ex,-3) && (!fixedwidth)) { if (NEAR(ey,h/2)) return /East; if (!fixedheight) { if (NEAR(ey,-3)) return /SouthEast; if (NEAR(ey,h+2)) return /NorthEast; } } else if (NEAR(ex,w/2) && (!fixedheight)) { if (NEAR(ey,-3)) return /South; if (NEAR(ey,h+2)) return /North; } return null; } rectangle seltrackr = [0,0,0,0]; float seltrackX = 0; float seltrackY = 0; boolean seltrackW = true; boolean seltrackH = true; rectangle seltrackrect(float X, float Y) { rectangle r; X = align2grid(X); Y = align2grid(Y); if (seltrackW && (abs(X-seltrackX) < minwidth)) X = seltrackX + ((X < seltrackX) ? -minwidth : minwidth); if (seltrackH && (abs(Y-seltrackY) < minheight)) Y = seltrackY + ((Y < seltrackY) ? -minheight : minheight); r = points2rect(X,Y,seltrackX,seltrackY); if (!seltrackW) r[0] = 0, r[2] = w; if (!seltrackH) r[1] = 0, r[3] = h; return r; } void seltrack() { rectangle r = seltrackrect(trackx,tracky); if (!cmp(r,seltrackr)) { stroke(); rectpath(insetstroke(-1,seltrackr = seltrackrect(trackx,tracky))); gsave(); stroke(); grestore(); } } boolean edit_reshape_handle() { float X, Y; name W; gsave(); concat(mx); X = mouseevent.XLocation; Y = mouseevent.YLocation; grestore(); if (W = which_reshape_handle(X,Y)) { switch (W) { case /East: seltrackX = w; case /West: seltrackH = false; break; case /South: seltrackY = h; case /North: seltrackW = false; break; case /SouthWest: seltrackY = h; case /NorthWest: break; case /SouthEast: seltrackY = h; case /NorthEast: seltrackX = w; break; } paint_selected(); gsave(); concat(mx); xor_mode(editSELcolor); rectpath(seltrackr); track(/seltrack); stroke(); grestore(); edit_reshape(x + seltrackr[0],y + seltrackr[1],seltrackr[2],seltrackr[3]); paint_selected(); unpromote(/seltrackr); unpromote(/seltrackX); unpromote(/seltrackY); unpromote(/seltrackW); unpromote(/seltrackH); return true; } return false; } /********************************************************************* * edit the selection */ boolean in_sel_path(eventtype e) { drawable m; forall (; m ; edit_sel) if (m.inpath(e)) return true; return false; } void edit_select_click() { drawable *s; int i; if ((length(edit_sel) == 1) && edit_sel[0].edit_reshape_handle()) return; if (in_sel_path(mouseevent)) { switch (track_state()) { case /mouse_drag: edit_drag_sel(); break; case /mouse_click: s = hitlist(mouseevent); if (length(edit_sel) > 1) { if (length(s)) s = (drawable *)getinterval(s,0,1); } else { i = arrayindex(s,edit_sel[0]); s = (drawable *)((i < 0) ? [] : getinterval(s,(i+1) % length(s),1)); } set_edit_sel(s); } } else { switch (track_state()) { case /mouse_drag: edit_select_from_rect(); break; case /mouse_click: s = hitlist(mouseevent); set_edit_sel(length(s) ? (drawable *)getinterval(s,0,1) : s); } } }