+ private static ChangedStatus CandidateAll = (ChangedStatus.CandidateList
+ | ChangedStatus.CandidateIndex
+ | ChangedStatus.CandidateShow);
+
+ [FlagsAttribute]
+ public enum KeyModifier
+ {
+ None = 0x00000000,
+ Shift_L = 0x00400000,
+ Shift_R = 0x00800000,
+ Shift = 0x00C00000,
+ Control_L = 0x01000000,
+ Control_R = 0x02000000,
+ Control = 0x03000000,
+ Alt_L = 0x04000000,
+ Alt_R = 0x08000000,
+ Alt = 0x0C000000,
+ AltGr = 0x10000000,
+ Super = 0x20000000,
+ Hyper = 0x40000000,
+ High = 0x70000000,
+ All = 0x7FC00000,
+ };
+
+ public struct Key
+ {
+ internal uint key;
+
+ private static Dictionary<string, uint> keysyms
+ = new Dictionary<string, uint> ();
+ private static Dictionary<string, KeyModifier> keymodifiers
+ = new Dictionary<string, KeyModifier> ();
+ private static uint keysym_base = 0x200000;
+ private static uint char_mask = ~((uint) KeyModifier.All);
+ public static readonly Key Reload;
+
+ static Key ()
+ {
+ keysyms["null"] = 0x00;
+ keysyms["bs"] = keysyms["backspace"] = 0x08;
+ keysyms["tab"] = 0x09;
+ keysyms["lf"] = keysyms["linefeed"] = 0x10;
+ keysyms["cr"] = keysyms["return"] = keysyms["enter"] = 0x13;
+ keysyms["esc"] = keysyms["escape"] = 0x1B;
+ keysyms["spc"] = keysyms["space"] = 0x20;
+ keysyms["del"] = keysyms["delete"] = 0x7F;
+ keymodifiers["shift-l"] = KeyModifier.Shift_L;
+ keymodifiers["shift-r"] = KeyModifier.Shift_R;
+ keymodifiers["shift"] = KeyModifier.Shift;
+ keymodifiers["control-l"] = KeyModifier.Control_L;
+ keymodifiers["control-r"] = KeyModifier.Control_R;
+ keymodifiers["control"] = KeyModifier.Control;
+ keymodifiers["alt-l"] = KeyModifier.Alt_L;
+ keymodifiers["alt-r"] = KeyModifier.Alt_R;
+ keymodifiers["alt"] = KeyModifier.Alt;
+ keymodifiers["altgr"] = KeyModifier.AltGr;
+ keymodifiers["super"] = KeyModifier.Super;
+ keymodifiers["hyper"] = KeyModifier.Hyper;
+ Reload = new Key ((MSymbol) "-reload");
+ }
+
+ private static uint decode_keysym (MSymbol keysym)
+ {
+ uint key;
+ string name = keysym.Name;
+
+ if (name.Length == 1)
+ return name[0];
+ name = name.ToLower ();
+ if (! keysyms.TryGetValue (name, out key))
+ keysyms[name] = key = keysym_base++;
+ return key;
+ }
+
+ private static uint combine_modifiers (uint c, KeyModifier modifiers)
+ {
+ if (c < 0x7F && c != 0x20)
+ {
+ if ((modifiers & KeyModifier.Shift) != KeyModifier.None
+ && Char.IsLower ((char) c))
+ {
+ modifiers &= ~KeyModifier.Shift;
+ c = Char.ToUpper ((char) c);
+ }
+ if ((modifiers & KeyModifier.Control) != KeyModifier.None)
+ {
+ modifiers &= ~KeyModifier.Control;
+ c &= 0x1F;
+ }
+ }
+ return c | (uint) modifiers;
+ }
+
+ public Key (uint c) { key = c; }
+ public Key (int c) { key = (uint) c; }
+
+ public Key (uint c, KeyModifier modifiers)
+ {
+ key = combine_modifiers (c, modifiers);
+ }
+
+ public Key (MSymbol keysym, KeyModifier modifiers)
+ {
+ key = combine_modifiers (decode_keysym (keysym), modifiers);
+ }
+
+ public Key (MSymbol keysym)
+ {
+ string str = keysym.Name;
+ int len = str.Length;
+ int i;
+ KeyModifier modifiers = KeyModifier.None;
+
+ for (i = 0; i + 2 < len && str[i + 1] == '-'; i += 2)
+ {
+ if (str[i] == 'S')
+ modifiers |= KeyModifier.Shift;
+ else if (str[i] == 'C')
+ modifiers |= KeyModifier.Control;
+ else if (str[i] == 'A')
+ modifiers |= KeyModifier.Alt;
+ else if (str[i] == 'G')
+ modifiers |= KeyModifier.AltGr;
+ else if (str[i] == 's')
+ modifiers |= KeyModifier.Super;
+ else if (str[i] == 'H')
+ modifiers |= KeyModifier.Hyper;
+ }
+ if (i + 1 == len)
+ key = combine_modifiers (str[i], modifiers);
+ else
+ key = combine_modifiers (decode_keysym (keysym), modifiers);
+ }
+
+ public Key (MPlist plist)
+ {
+ KeyModifier modifiers = KeyModifier.None;
+ MPlist p;
+
+ for (p = plist; ! p.IsEmpty; p = p.next)
+ {
+ if (p.IsInteger)
+ {
+ if (! p.next.IsEmpty)
+ throw new Exception ("Invalid Key: " + plist);
+ break;
+ }
+ else if (! p.IsSymbol)
+ throw new Exception ("Invalid Key: " + plist);
+ else
+ {
+ string name = p.Symbol.Name.ToLower ();
+ KeyModifier m;
+
+ if (! keymodifiers.TryGetValue (name, out m))
+ break;
+ modifiers |= m;
+ }
+ }
+ if (p.IsEmpty || ! p.next.IsEmpty)
+ throw new Exception ("Invalid Key: " + plist);
+ if (p.IsInteger)
+ key = combine_modifiers ((uint) p.Integer, modifiers);
+ else
+ key = combine_modifiers (decode_keysym (p.Symbol), modifiers);
+ }
+
+ public bool HasModifier
+ {
+ get { return ((key & (uint) KeyModifier.All) != 0); }
+ }
+
+ public static bool operator== (Key k1, Key k2)
+ {
+ return k1.key == k2.key;
+ }
+
+ public static bool operator!= (Key k1, Key k2)
+ {
+ return k1.key != k2.key;
+ }
+
+ public override bool Equals (object o) { return key == ((Key) o).key; }
+
+ public override int GetHashCode () { return (int) key; }
+
+ public bool Match (Key k)
+ {
+ if (k.key == key)
+ return true;
+ if ((k.key & char_mask) != (key & char_mask))
+ return false;
+ KeyModifier m1 = ((KeyModifier) key) & KeyModifier.All;
+ KeyModifier m2 = ((KeyModifier) k.key) & KeyModifier.All;
+ return (((m1 & KeyModifier.Shift) == (m2 & KeyModifier.Shift)
+ || ((m1 & KeyModifier.Shift) == KeyModifier.Shift
+ && (m2 & KeyModifier.Shift) != KeyModifier.None))
+ && ((m1 & KeyModifier.Control) == (m2 & KeyModifier.Control)
+ || ((m1 & KeyModifier.Control) == KeyModifier.Control
+ && (m2 & KeyModifier.Control) != KeyModifier.None))
+ && ((m1 & KeyModifier.Alt) == (m2 & KeyModifier.Alt)
+ || ((m1 & KeyModifier.Alt) == KeyModifier.Alt
+ && (m2 & KeyModifier.Alt) != KeyModifier.None))
+ && ((m1 & KeyModifier.High) == (m2 & KeyModifier.High)));
+ }
+
+ public int ToChar ()
+ {
+ return (key & 0x3FFFFF) <= 0x1FFFFF ? (int) (key & 0x1FFFFF) : -1;
+ }
+
+ public override string ToString ()
+ {
+ int c = ToChar ();
+ MText mt = null;
+ if (c < 0x20)
+ foreach (KeyValuePair<string, uint> kv in keysyms)
+ if ((key & 0x3FFFFF) == kv.Value)
+ {
+ mt = kv.Key;
+ break;
+ }
+ if (mt == null)
+ mt = new MText (c);
+
+ KeyModifier m = ((KeyModifier) key) & KeyModifier.All;
+
+ if (m != KeyModifier.None)
+ {
+ if ((m & KeyModifier.Shift) != KeyModifier.None)
+ mt.Ins (0, "S-");
+ if ((m & KeyModifier.Control) != KeyModifier.None)
+ mt.Ins (0, "C-");
+ if ((m & KeyModifier.Alt) != KeyModifier.None)
+ mt.Ins (0, "A-");
+ if ((m & KeyModifier.AltGr) != KeyModifier.None)
+ mt.Ins (0, "G-");
+ if ((m & KeyModifier.Super) != KeyModifier.None)
+ mt.Ins (0, "s-");
+ if ((m & KeyModifier.Hyper) != KeyModifier.None)
+ mt.Ins (0, "H-");
+ }
+ return (string) mt;
+ }
+ }
+
+ internal class KeySeq : Xex.TermValue
+ {
+ private static Xex.Symbol name = "keyseq";
+ public static Xex.Symbol Name { get { return name; } }
+
+ public List<Key> keyseq = new List<Key> ();
+
+ public override Xex.TermValue Clone ()
+ {
+ KeySeq ks = new KeySeq ();
+ ks.keyseq.InsertRange (0, keyseq);
+ return ks;
+ }
+
+ public KeySeq () { }
+
+ public KeySeq (MPlist plist)
+ {
+ foreach (MPlist p in plist)
+ {
+ if (p.IsSymbol)
+ keyseq.Add (new Key (p.Symbol));
+ else if (p.IsInteger)
+ keyseq.Add (new Key ((char) p.Integer));
+ else if (p.IsPlist)
+ keyseq.Add (new Key (p.Plist));
+ else
+ throw new Exception ("Invalid Key Sequence: " + plist);
+ }
+ }
+
+ public KeySeq (MText mt) : base ()
+ {
+ for (int i = 0; i < mt.Length; i++)
+ keyseq.Add (new Key ((uint) mt[i]));
+ }
+
+ public KeySeq (List<Xex.Term> list)
+ {
+ int len = list.Count;
+
+ for (int i = 0; i < len; i++)
+ {
+ if (list[i].IsInt)
+ keyseq.Add (new Key (list[i].Intval));
+ else if (list[i].IsStr)
+ keyseq.Add (new Key (list[i].Strval.ToString ()));
+ else if (list[i].IsSymbol)
+ keyseq.Add (new Key ((string) list[i].Symval));
+ else
+ throw new Exception ("Invalid key: " + list[i]);
+ }
+ }
+
+ public static Xex.TermValue Parser (Xex.Domain domain, XmlNode node)
+ {
+ Xex.Term term = Xex.Parse (domain, node.FirstChild);
+ term = Xex.Eval (domain, term);
+ return (term.IsStr ? new KeySeq ((MText) term.Strval.ToString ())
+ : new KeySeq (term.Listval));
+ }
+
+ public override bool Equals (object obj)
+ {
+ KeySeq ks = obj as KeySeq;
+ if (ks == null || ks.keyseq.Count != keyseq.Count)
+ return false;
+ for (int i = 0; i < keyseq.Count; i++)
+ if (keyseq[i] != ks.keyseq[i])
+ return false;
+ return true;
+ }
+
+ public override int GetHashCode ()
+ {
+ int code = 0;
+ for (int i = 0; i < keyseq.Count; i++)
+ code ^= keyseq[i].GetHashCode ();
+ return code;
+ }
+
+ public override string ToString ()
+ {
+ MText mt;
+ foreach (Key key in keyseq)
+ if (key.HasModifier || key.ToChar () < 0x20)
+ {
+ mt = "(";
+ foreach (Key k in keyseq)
+ {
+ if (mt.Length > 1)
+ mt.Cat (' ');
+ mt.Cat (k.ToString ());
+ }
+ return (string) mt.Cat (")");
+ }
+ mt = "\"";
+ foreach (Key k in keyseq)
+ {
+ int c = k.ToChar ();
+
+ if (c == '\\' || c == '"')
+ mt.Cat ('\\');
+ mt.Cat (c);
+ }
+ return (string) mt.Cat ("\"");
+ }
+ }
+
+ public class Command
+ {
+ public MSymbol name;
+ public MText description;
+ internal List<KeySeq> keys;
+
+ public Command (MPlist p)
+ {
+ name = p.Symbol;
+ p = p.Next;
+ description = parse_description (p);
+ if (description == null)
+ description = "No description";
+ keys = new List<KeySeq> ();
+ for (p = p.next; ! p.IsEmpty; p = p.next)
+ {
+ if (p.IsMText)
+ keys.Add (new KeySeq (p.Text));
+ else if (p.IsPlist)
+ keys.Add (new KeySeq (p.Plist));
+ }
+ }
+
+ public Command (XmlNode node)
+ {
+ name = node.Attributes[0].Value;
+ keys = new List<KeySeq> ();
+ for (node = node.FirstChild; node != null; node = node.NextSibling)
+ {
+ if (node.Name == "description")
+ description = parse_description (node);
+ else if (node.Name == "keyseq")
+ keys.Add ((KeySeq) KeySeq.Parser (null, node));
+ }
+ }
+
+ public override string ToString ()
+ {
+ string str = "(" + name + " \"" + (string) description;
+ foreach (KeySeq keyseq in keys)
+ str += " " + keyseq;
+ return str + ")";
+ }
+ }
+
+ internal class Plugin
+ {
+ private string name;
+ private Assembly assembly;
+ private Type plugin_type;
+
+ public Plugin (string name)
+ {
+ this.name = name;
+ }
+
+ public MethodInfo GetMethod (Xex.Symbol name)
+ {
+ if (assembly == null)
+ {
+ assembly = Assembly.LoadFrom (name + ".dll");
+ plugin_type = assembly.GetType ("M17n.MInputMethod.Plugin");
+ }
+
+ MethodInfo info = plugin_type.GetMethod ((string) name);
+ if (info == null)
+ throw new Exception ("Invalid plugin method: " + name);
+ return info;
+ }
+
+ public override string ToString ()
+ {
+ return String.Format ("(module {0}", name);
+ }
+ }
+
+ internal class PluginMethod : Xex.Function
+ {
+ private Plugin plugin;
+ private MethodInfo method_info;
+ object[] parameters = new object[2];
+
+ public PluginMethod (Plugin plugin, string name)
+ : base ((Xex.Symbol) name, false, 0, -1)
+ {
+ this.plugin = plugin;
+ }
+
+ public override Xex.Term Call (Xex.Domain domain, Xex.Variable vari,
+ Xex.Term[] args)
+ {
+ args = (Xex.Term[]) args.Clone ();
+ for (int i = 0; i < args.Length; i++)
+ {
+ args[i] = Xex.Eval (domain, args[i]);
+ if (domain.Thrown ())
+ return args[i];
+ }
+ if (method_info == null)
+ method_info = plugin.GetMethod (name);
+ parameters[0] = domain.context;
+ parameters[1] = args;
+ return (Xex.Term) method_info.Invoke (null, parameters);
+ }
+ }
+
+ internal abstract class Marker : Xex.TermValue
+ {
+ private static Xex.Symbol name = "marker";
+ public static Xex.Symbol Name { get { return name; } }
+
+ private MSymbol mname;
+
+ private Marker (MSymbol mname)
+ {
+ this.mname = mname;
+ }
+
+ public abstract int Position (Context ic);
+
+ public virtual void Mark (Context ic)
+ {
+ throw new Exception ("Can't set predefined marker: " + mname);
+ }
+ public virtual int CharAt (Context ic)
+ {
+ int pos = Position (ic);
+
+ return ((pos >= 0 && pos < ic.preedit.Length) ? ic.preedit[pos]
+ : -1);
+ }
+
+ public override string ToString ()
+ {
+ return "<marker>" + mname + "</marker>";
+ }
+
+ public static Xex.TermValue Parser (Xex.Domain domain, XmlNode node)
+ {
+ return Get ((MSymbol) node.InnerText);
+ }
+
+
+ public override bool Equals (object obj)
+ {
+ Marker m = obj as Marker;
+ return (m != null && m.mname == mname);
+ }
+
+ public override int GetHashCode () { return mname.GetHashCode (); }
+
+ public class Named : Marker
+ {
+ public Named (MSymbol mname) : base (mname) { }
+
+ public override int Position (Context ic)
+ {
+ MPlist p = ic.marker_positions.Find (mname);
+ return (p == null ? 0 : p.Integer);
+ }
+
+ public override void Mark (Context ic)
+ {
+ ic.marker_positions.Put (mname, ic.cursor_pos);
+ }
+ }
+
+ public class Predefined : Marker
+ {
+ char tag;
+
+ public Predefined (char tag) : base ("@" + tag) { this.tag = tag; }
+
+ public override int Position (Context ic)
+ {
+ switch (tag) {
+ case '<': return 0;
+ case '>': return ic.preedit.Length;
+ case '-': return ic.cursor_pos - 1;
+ case '+': return ic.cursor_pos + 1;
+ case '[':
+ if (ic.cursor_pos > 0)
+ {
+ int pos = ic.cursor_pos;
+ int to;
+ ic.preedit.FindProp (Mcandidates, pos - 1, out pos, out to);
+ return pos;
+ }
+ return 0;
+ case ']':
+ if (ic.cursor_pos < ic.preedit.Length - 1)
+ {
+ int pos = ic.cursor_pos;
+ int from;
+ ic.preedit.FindProp (Mcandidates, pos, out from, out pos);
+ return pos;
+ }
+ return ic.preedit.Length;
+ default:
+ return tag - '0';
+ }
+ }
+ }
+
+ public class PredefinedAbsolute : Marker
+ {
+ private int pos;
+
+ public PredefinedAbsolute (MSymbol mname) : base (mname)
+ {
+ if (! int.TryParse (((string) mname).Substring (1), out pos))
+ throw new Exception ("Invalid marker name: " + mname);
+ }
+
+ public override int Position (Context ic)
+ {
+ return (pos < ic.preedit.Length ? pos : ic.preedit.Length);
+ }
+ }
+
+ public class PredefinedSurround : Marker
+ {
+ private int distance;
+
+ public PredefinedSurround (MSymbol mname) : base (mname)
+ {
+ if (! int.TryParse (((string) name).Substring (1), out distance))
+ throw new Exception ("Invalid marker name: " + mname);
+ if (distance > 0)
+ distance--;
+ }
+
+ public override int Position (Context ic)
+ {
+ return ic.cursor_pos + distance;
+ }
+
+ public override int CharAt (Context ic)
+ {
+ int pos = ic.cursor_pos + distance;
+ if (pos < 0)
+ return ic.GetSurroundingChar (pos);
+ else if (pos >= ic.preedit.Length)
+ return ic.GetSurroundingChar (pos - ic.preedit.Length);
+ return ic.preedit[pos];
+ }
+ }
+
+ static internal Dictionary<MSymbol,Predefined> predefineds;
+
+ static Marker ()
+ {
+ predefineds = new Dictionary<MSymbol, Predefined> ();
+ predefineds ["@<"] = predefineds["@first"] = new Predefined ('<');
+ predefineds ["@>"] = predefineds["@last"] = new Predefined ('>');
+ predefineds ["@-"] = predefineds["@previous"] = new Predefined ('-');
+ predefineds ["@+"] = predefineds["@next"] = new Predefined ('+');
+ predefineds ["@["] = predefineds["@previous-candidate-change"]
+ = new Predefined ('[');
+ predefineds ["@]"] = predefineds["@next-candidate-change"]
+ = new Predefined (']');
+ }
+
+ public static Marker Get (MSymbol mname)
+ {
+ string str = mname.Name;
+ if (str[0] == '@')
+ {
+ Predefined pred;
+ if (predefineds.TryGetValue (mname, out pred))
+ return pred;
+ if (str.Length == 1)
+ throw new Exception ("Invalid marker name: " + mname);
+ if (Char.IsDigit (str[1]))
+ return new PredefinedAbsolute (mname);
+ if (str.Length == 2 || mname == Mat_minus_zero
+ || ! (str[1] == '-' || str[1] == '+'))
+ throw new Exception ("Invalid marker name: " + mname);
+ return new PredefinedSurround (mname);
+ }
+ return new Named (mname);
+ }
+ }
+
+ internal class Candidates
+ {
+ private class Block
+ {
+ public int Index;
+ public object Data;
+
+ public Block (int index, Xex.Term term)
+ {
+ Index = index;
+ if (term.IsStr)
+ Data = (MText) term.Strval.ToString ();
+ else
+ {
+ MPlist plist = new MPlist ();
+ MPlist p = plist;
+ foreach (Xex.Term t in term.Listval)
+ p = p.Add (MSymbol.mtext, (MText) t.Strval.ToString ());
+ Data = plist;
+ }
+ }
+
+ public Block (int index, MPlist plist)
+ {
+ Index = index;
+ if (plist.IsMText)
+ Data = plist.Text;
+ else if (plist.IsPlist)
+ Data = plist.Plist;
+ else
+ throw new Exception ("Invalid candidate: " + plist);
+ }
+
+ public int Count
+ {
+ get { return (Data is MText
+ ? ((MText) Data).Length
+ : ((MPlist) Data).Count); }
+ }
+
+ public object this[int i]
+ {
+ get {
+ if (Data is MText) return ((MText) Data)[i];
+ return ((MPlist) Data)[i];
+ }
+ }
+ }
+
+ private Block[] blocks;
+ private int row = 0;
+ private int index = 0;
+ public object[] group;
+
+ private bool IsFixed { get { return group != null; } }
+ private int Total {
+ get {
+ Block last = blocks[blocks.Length - 1];
+ return last.Index + last.Count; }
+ }
+
+ public int Column {
+ get { return (IsFixed ? index % group.Length
+ : index - blocks[row].Index); }
+ }
+
+ public object Group {
+ get { return (IsFixed ? group : blocks[row].Data); }
+ }
+
+ public int GroupLength
+ {
+ get {
+ if (IsFixed)
+ {
+ int nitems = group.Length;
+ int start = index - (index % nitems);
+ int total = Total;
+ return (start + nitems <= total ? nitems : total - start);
+ }
+ return blocks[row].Count;
+ }
+ }
+
+ public object Current {
+ get {
+ return (IsFixed ? group[index % group.Length]
+ : blocks[row][index - blocks[row].Index]);
+ }
+ }
+
+ public Candidates (MPlist list, int column)
+ {
+ int nblocks = list.Count;
+
+ blocks = new Block[nblocks];
+ for (int i = 0, start = 0; i < nblocks; i++, list = list.next)
+ start += (blocks[i] = new Block (index, list)).Count;
+ if (column > 0)
+ {
+ group = new object[column];
+ fill_group (0);
+ }
+ }
+
+ public Candidates (Xex.Term[] candidates, int column)
+ {
+ int nblocks = candidates.Length;
+
+ blocks = new Block[nblocks];
+ for (int i = 0, start = 0; i < nblocks; i++)
+ start += (blocks[i] = new Block (index, candidates[i])).Count;
+ if (column > 0)
+ {
+ group = new object[column];
+ fill_group (0);
+ }
+ }
+
+ public static void Detach (Context ic)
+ {
+ ic.preedit.PopProp (0, ic.preedit.Length, Mcandidates);
+ ic.candidates = null;
+ ic.changed |= (ChangedStatus.Preedit | ChangedStatus.CursorPos
+ | CandidateAll);
+ }
+
+ // Fill the array "group" by candidates stating from START.
+ // START must be a multiple of "column". Return the number of
+ // valid candidates in "group".
+
+ private int fill_group (int start)
+ {
+ int nitems = group.Length;
+ int r = row;
+ Block b = blocks[r];
+
+ if (start < b.Index)
+ while (start < b.Index)
+ b = blocks[--r];
+ else
+ while (start >= b.Index + b.Count)
+ b = blocks[++r];
+ row = r;
+
+ int count = b.Count;
+ start -= b.Index;
+ for (int i = 0; i < nitems; i++, start++)
+ {
+ if (start >= count)
+ {
+ r++;
+ if (r == blocks.Length)
+ return i;
+ b = blocks[r];
+ count = b.Count;
+ start = 0;
+ }
+ group[i] = b[start];
+ }
+ return nitems;
+ }
+
+ // Update "row" to what contains the first candidate of
+ // the previous candidate-group, update "current_index", and
+ // update "group" if necessary. Return the previous
+ // candidate-group. Set NITEMS to the number of valid
+ // candidates contained in that group.
+
+ public int PrevGroup ()
+ {
+ int nitems;
+ int col = Column;
+
+ if (IsFixed)
+ {
+ nitems = group.Length;
+ if ((index -= col + nitems) < 0)
+ index = (Total / nitems) * nitems;
+ nitems = fill_group (index);
+ }
+ else
+ {
+ row = row > 0 ? row-- : blocks.Length - 1;
+ nitems = blocks[row].Count;
+ index = blocks[row].Index;
+ }
+ index += col < nitems ? col : nitems - 1;
+ return nitems;
+ }
+
+ public int NextGroup ()
+ {
+ int nitems;
+ int col = Column;
+
+ if (IsFixed)
+ {
+ nitems = group.Length;
+ if ((index += nitems - col) >= Total)
+ index = 0;
+ nitems = fill_group (index);
+ }
+ else
+ {
+ row = row < blocks.Length - 1 ? row + 1 : 0;
+ nitems = blocks[row].Count;
+ index = blocks[row].Count;
+ }
+ index += col < nitems ? col : nitems - 1;
+ return nitems;
+ }
+
+ public void Prev ()
+ {
+ int col = Column;
+
+ if (col == 0)
+ {
+ int nitems = PrevGroup ();
+ index += col < nitems - 1 ? col : nitems - 1;
+ }
+ else
+ index--;
+ }
+
+ public void Next ()
+ {
+ int col = Column;
+ int nitems = GroupLength;
+
+ if (col == nitems - 1)
+ {
+ nitems = NextGroup ();
+ index -= Column;
+ }
+ else
+ index++;
+ }
+
+ public void First ()
+ {
+ index -= Column;
+ }
+
+ public void Last ()
+ {
+ index += GroupLength - (Column + 1);
+ }
+
+ public object Select (int col)
+ {
+ int maxcol = GroupLength - 1;
+ if (col > maxcol)
+ col = maxcol;
+ index = index - Column + col;
+ return Current;
+ }
+
+ public object Select (Selector selector)
+ {
+ switch (selector.Tag)
+ {
+ case '<': First (); break;
+ case '>': Last (); break;
+ case '-': Prev (); break;
+ case '+': Next (); break;
+ case '[': PrevGroup (); break;
+ case ']': NextGroup (); break;
+ default: break;
+ }
+ return Current;
+ }
+
+ public override string ToString ()
+ {
+ return (String.Format ("<candidates row={0} col={1}>", row, index)
+ + Group
+ + "</candidates>");
+ }
+ }
+
+ internal class Selector : Xex.TermValue