MODULE ObxGraphs;
(**

   project   = "BlackBox"
   organization   = "www.oberon.ch"
   contributors   = "Oberon microsystems"
   version   = "System/Rsrc/About"
   copyright   = "System/Rsrc/About"
   license   = "Docu/BB-License"
   changes   = ""
   issues   = ""

**)

   IMPORT Services, Stores, Ports, Models, Views, Controllers, Properties, TextModels, TextViews, TextMappers;

   CONST minVersion = 0; maxVersion = 0;

   TYPE

      Value = POINTER TO RECORD
         next: Value;
         val: INTEGER
      END;
      Model = POINTER TO RECORD (Models.Model)

         values: Value
      END;
      View = POINTER TO RECORD (Views.View)

         model: Model
      END;
      ModelOp = POINTER TO RECORD (Stores.Operation)

         model: Model;
         values: Value
      END;
   PROCEDURE (op: ModelOp) Do;

      VAR v: Value; msg: Models.UpdateMsg;
   BEGIN
      v := op.model.values; op.model.values := op.values; op.values := v;   (* swap *)
      Models.Broadcast(op.model, msg)
   END Do;
   PROCEDURE (m: Model) Internalize (VAR rd: Stores.Reader);


      VAR version: INTEGER; n: INTEGER; v, last: Value;
   BEGIN
      rd.ReadVersion(minVersion, maxVersion, version);
      IF ~rd.cancelled THEN
         last := NIL;
         rd.ReadInt(n);   (* read number of values *)
         WHILE n # 0 DO
            NEW(v); rd.ReadInt(v.val);
            IF last = NIL THEN m.values := v ELSE last.next := v END;   (* append value *)
            last := v;
            DEC(n)
         END
      END
   END Internalize;
   PROCEDURE (m: Model) Externalize (VAR wr: Stores.Writer);

      VAR v: Value; n: INTEGER;
   BEGIN
      wr.WriteVersion(maxVersion);
      v := m.values; n := 0; WHILE v # NIL DO INC(n); v := v.next END;
      wr.WriteInt(n);   (* write number of values *)
      v := m.values; WHILE v # NIL DO wr.WriteInt(v.val); v := v.next END
   END Externalize;
   PROCEDURE (m: Model) CopyFrom (source: Stores.Store);

   BEGIN
      m.values := source(Model).values   (* values are immutable and thus can be shared *)
   END CopyFrom;
   PROCEDURE (m: Model) SetValues (v: Value), NEW;

      VAR op: ModelOp;
   BEGIN
      NEW(op); op.model := m; op.values := v;
      Models.Do(m, "Set Values", op)
   END SetValues;
   PROCEDURE OpenData (v: View);


      VAR t: TextModels.Model; f: TextMappers.Formatter; h: Value;
   BEGIN
      t := TextModels.dir.New();
      f.ConnectTo(t);
      h := v.model.values;
      WHILE h # NIL DO
         f.WriteInt(h.val); f.WriteLn;
         h := h.next
      END;
      Views.OpenAux(TextViews.dir.New(t), "Values")
   END OpenData;
   PROCEDURE DropData (t: TextModels.Model; v: View);

      VAR s: TextMappers.Scanner; first, last, h: Value;
   BEGIN
      s.ConnectTo(t);
      s.Scan;
      WHILE s.type = TextMappers.int DO
         NEW(h); h.val := s.int;
         IF last = NIL THEN first := h ELSE last.next := h END;
         last := h;
         s.Scan
      END;
      v.model.SetValues(first)
   END DropData;
   PROCEDURE (v: View) Internalize (VAR rd: Stores.Reader);


      VAR version: INTEGER; st: Stores.Store;
   BEGIN
      rd.ReadVersion(minVersion, maxVersion, version);
      IF ~rd.cancelled THEN
         rd.ReadStore(st);
         v.model := st(Model)
      END
   END Internalize;
   PROCEDURE (v: View) CopyFromModelView (source: Views.View; model: Models.Model);

   BEGIN
      ASSERT(model IS Model, 20);
      WITH source: View DO
         v.model := model(Model)
      END
   END CopyFromModelView;
   PROCEDURE (v: View) Externalize (VAR wr: Stores.Writer);

   BEGIN
      wr.WriteVersion(maxVersion);
      wr.WriteStore(v.model)
   END Externalize;
   PROCEDURE (v: View) ThisModel (): Models.Model;

   BEGIN
      RETURN v.model
   END ThisModel;
   PROCEDURE (v: View) Restore (f: Views.Frame; l, t, r, b: INTEGER);

      VAR h: Value; n: INTEGER; width, height, d, x: INTEGER;
   BEGIN
      h := v.model.values; n := 0; WHILE h # NIL DO INC(n); h := h.next END;   (* count values *)
      IF n > 0 THEN
         v.context.GetSize(width, height);
         d := width DIV n; x := 0;
         h := v.model.values;
         WHILE h # NIL DO
            f.DrawRect(x, height - h.val * Ports.mm, x + d, height, Ports.fill, Ports.grey50);
            h := h.next; INC(x, d)
         END
      END
   END Restore;
   PROCEDURE (v: View) HandleModelMsg (VAR msg: Models.Message);

   BEGIN
      WITH msg: Models.UpdateMsg DO
         Views.Update(v, Views.keepFrames)
      ELSE
      END
   END HandleModelMsg;
   PROCEDURE (v: View) HandleCtrlMsg (f: Views.Frame; VAR msg: Views.CtrlMessage;

                                                VAR focus: Views.View);
      VAR x, y, w, h: INTEGER; modifiers: SET; isDown: BOOLEAN;
   BEGIN
      WITH msg: Controllers.TrackMsg DO
         REPEAT f.Input(x, y, modifiers, isDown) UNTIL ~isDown;
         v.context.GetSize(w, h);
         IF (x >= 0) & (y >= 0) & (x < w) & (y < h) THEN OpenData(v) END
      | msg: Controllers.PollDropMsg DO
         IF Services.Extends(msg.type, "TextViews.View") THEN
            msg.dest := f (* enable drop target feedback *)
         END
      | msg: Controllers.DropMsg DO
         IF msg.view IS TextViews.View THEN
            DropData(msg.view(TextViews.View).ThisModel(), v)   (* interpret dropped text *)
         END
      ELSE
      END
   END HandleCtrlMsg;
   PROCEDURE (v: View) HandlePropMsg (VAR msg: Properties.Message);

      CONST min = 10 * Ports.mm; max = 160 * Ports.mm; pref = 90 * Ports.mm;
   BEGIN
      WITH msg: Properties.SizePref DO   (* prevent illegal sizes *)
         IF msg.w = Views.undefined THEN msg.w := pref
         ELSIF msg.w < min THEN msg.w := min
         ELSIF msg.w > max THEN msg.w := max
         END;
         IF msg.h = Views.undefined THEN msg.h := pref
         ELSIF msg.h < min THEN msg.h := min
         ELSIF msg.h > max THEN msg.h := max
         END
      | msg: Properties.FocusPref DO
         msg.hotFocus := TRUE
      ELSE
      END
   END HandlePropMsg;
   PROCEDURE Deposit*;

      VAR m: Model; v: View;
   BEGIN
      NEW(m);
      NEW(v); v.model := m; Stores.Join(v, m);
      Views.Deposit(v)
   END Deposit;
END ObxGraphs.