context: /*
      Filezoo - a small and fast file manager
      Copyright (C) 2008  Ilmari Heikkinen
  
      This program is free software: you can redistribute it and/or modify
      it under the terms of the GNU General Public License as published by
      the Free Software Foundation, either version 3 of the License, or
      (at your option) any later version.
  
      This program is distributed in the hope that it will be useful,
      but WITHOUT ANY WARRANTY; without even the implied warranty of
      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      GNU General Public License for more details.
  
      You should have received a copy of the GNU General Public License
      along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
  
  using System;
  using System.Collections.Generic;
  using System.Diagnostics;
  using System.Threading;
  using System.Timers;
  using System.IO;
  using Gtk;
  using Cairo;
  using Mono.Unix;
  
  public class Filezoo : DrawingArea
  {
    // Filename Unicode icons
    public Dictionary<string, string> Prefixes = null;
  
    public string BreadcrumbFontFamily = "Sans";
    public string ToolbarTitleFontFamily = "Sans";
    public string ToolbarLabelFontFamily = "Sans";
  
    public string FileNameFontFamily = "Sans";
    public string FileInfoFontFamily = "Sans";
  
    // current directory style
    public double BreadcrumbFontSize = 12;
    public double BreadcrumbMarginTop = 6;
    public double BreadcrumbMarginLeft = 12;
    public double BreadcrumbMarginRight = 12;
  
    // sort/size toolbar style
    public double ToolbarY = 24;
    public double ToolbarTitleFontSize = 6;
    public double ToolbarLabelFontSize = 9;
  
    public string SortLabel = "Sort ";
    public string SizeLabel = "Size ";
  
    public Color ActiveColor = new Color (0,0,0,1);
    public Color InActiveColor = new Color (0,0,0,0.5);
  
    // filesystem view style
    public double FilesMarginLeft = 10;
    public double FilesMarginRight = 0;
    public double FilesMarginTop = 52;
    public double FilesMarginBottom = 0;
  
    // zoom speed settings, must be > 1 to zoom in the right direction
    public double ZoomInSpeed = 2;
    public double ZoomOutSpeed = 2;
  
    // Available sorts
    public SortHandler[] SortFields = {
      new SortHandler("Type", new TypeComparer()),
      new SortHandler("Name", new NameComparer()),
      new SortHandler("Size", new SizeComparer()),
      new SortHandler("Date", new DateComparer())
    };
    // current sort settings
    public SortHandler SortField;
    public SortingDirection SortDirection = SortingDirection.Ascending;
  
    // Available file sizers
    public SizeHandler[] SizeFields = {
      new SizeHandler("Flat", new FlatMeasurer()),
      new SizeHandler("Size", new SizeMeasurer()),
      new SizeHandler("Date", new DateMeasurer()),
      new SizeHandler("Entries", new EntriesMeasurer()),
  //     new SizeHandler("Count", new CountMeasurer()),
      new SizeHandler("Total", new TotalMeasurer())
    };
    // current file sizer
    public SizeHandler SizeField;
  
    // current zoomer
    public IZoomer Zoomer;
  
    // current directory
    public string CurrentDirPath = null;
    public FSEntry CurrentDirEntry;
  
    // Do we need to redraw?
    bool NeedRedraw = true;
  
    // Do we need to redraw the filesystem view?
    bool FSNeedRedraw = true;
  
    // Whether to quit after startup
    public bool QuitAfterFirstFrame = false;
  
    // are we drawing the first frame of a new directory
    bool FirstFrameOfDir = true;
  
    // GUI state variables
    bool dragging = false;
    double dragStartX = 0.0;
    double dragStartY = 0.0;
    double dragX = 200.0;
    double dragY = -200.0;
  
    public uint Width = 1;
    public uint Height = 1;
  
    bool EffectInProgress = false;
  
    // first frame latency profiler
    Profiler dirLatencyProfiler = new Profiler ("----", 100);
  
    // interaction profiler, time from user event to draw complete
    Profiler InteractionProfiler = new Profiler ("UI", 30);
  
    // empty surface for PreDraw context.
    ImageSurface PreDrawSurface = new ImageSurface (Format.A1, 1, 1);
  
    // empty surface for etc context.
    ImageSurface EtcSurface = new ImageSurface (Format.A1, 1, 1);
  
    // Surface for main view.
    ImageSurface CachedSurface;
  
  
    // modification monitor
    DateTime LastRedraw = DateTime.Now;
  
    bool PreDrawComplete = true;
  
    Menu ContextMenu;
  
    public FSDraw Renderer;
  
    /* Constructor */
  
    private static TargetEntry [] target_table = new TargetEntry [] {
      new TargetEntry ("text/uri-list", 0, 0),
      new TargetEntry ("application/x-color", 0, 1),
      new TargetEntry ("text/plain", 0, 2),
      new TargetEntry ("application/octet-stream", 0, 2),
      new TargetEntry ("STRING", 0, 2)
    };
  
    TargetEntry[] targets = new TargetEntry[] {
      new TargetEntry ("text/uri-list", 0, 0),
      new TargetEntry ("text/plain", 0, 2),
      new TargetEntry ("STRING", 0, 2)
    };
  
    FSEntry DragSourceEntry;
    string DragSourcePath;
  
    public bool Cancelled = false;
  
    bool UseRgbaVisuals = false;
  
    public Dictionary<string,bool> Selection = new Dictionary<string,bool> ();
  
    Clipboard clipboard;
  
    Gdk.Cursor clickCursor = new Gdk.Cursor (Gdk.CursorType.Hand2);
  
    Gdk.Cursor defaultCursor = new Gdk.Cursor (Gdk.CursorType.Arrow);
  
    Gdk.Cursor panCursor = new Gdk.Cursor (Gdk.CursorType.Hand1);
    Gdk.Cursor dragCursor = new Gdk.Cursor (Gdk.CursorType.Hand1);
  
    Gdk.Cursor copyCursor = new Gdk.Cursor (Gdk.CursorType.LeftPtr);
    Gdk.Cursor shiftCopyCursor = new Gdk.Cursor (Gdk.CursorType.RightPtr);
    Gdk.Cursor clearSelCursor = new Gdk.Cursor (Gdk.CursorType.Circle);
  
    /** BLOCKING - startup dir latency */
    public Filezoo (string dirname)
    {
      Selection = new Dictionary<string, bool> ();
      Renderer = new FSDraw ();
  
      clipboard = Clipboard.Get(Gdk.Atom.Intern("CLIPBOARD", true));
  
      SortField = SortFields[0];
      SizeField = SizeFields[0];
      Zoomer = new FlatZoomer ();
  
      Gdk.Colormap cm = Screen.RgbaColormap;
      if (cm != null && Screen.IsComposited) {
        Widget.DefaultColormap = cm;
        Colormap = cm;
        UseRgbaVisuals = true;
      }
  
      SetCurrentDir (dirname);
  
      Helpers.StartupProfiler.Time ("SetCurrentDir");
  
      CanFocus = true;
      KeyPressEvent += delegate (object o, KeyPressEventArgs args) {
        Cancelled = true;
        Gdk.ModifierType state = args.Event.State;
        switch (args.Event.Key) {
          case Gdk.Key.Control_L:
          case Gdk.Key.Control_R:
            state |= Gdk.ModifierType.ControlMask;
            break;
          case Gdk.Key.Shift_L:
          case Gdk.Key.Shift_R:
            state |= Gdk.ModifierType.ShiftMask;
            break;
          case Gdk.Key.Alt_L:
          case Gdk.Key.Alt_R:
            state |= Gdk.ModifierType.Mod1Mask;
            break;
        }
        SetCursor (state);
      };
  
      KeyReleaseEvent += delegate (object o, KeyReleaseEventArgs args) {
        Cancelled = true;
        if (args.Event.Key == Gdk.Key.Escape && Selection.Count > 0) {
          ClearSelection ();
          args.RetVal = true;
        } else if ((args.Event.State & Gdk.ModifierType.ControlMask) == Gdk.ModifierType.ControlMask) {
          switch (args.Event.Key) {
            case Gdk.Key.x:
              CutSelection(CurrentDirPath);
              break;
            case Gdk.Key.c:
              CopySelection(CurrentDirPath);
              break;
            case Gdk.Key.v:
              PasteSelection(CurrentDirPath);
              break;
          }
        } else {
          switch (args.Event.Key) {
            case Gdk.Key.Delete:
              TrashSelection ();
              break;
            case Gdk.Key.BackSpace:
              GoToParent ();
              break;
            case Gdk.Key.Home:
              SetCurrentDir (Helpers.HomeDir);
              break;
          }
        }
        Gdk.ModifierType state = args.Event.State;
        switch (args.Event.Key) {
          case Gdk.Key.Control_L:
          case Gdk.Key.Control_R:
            state &= ~Gdk.ModifierType.ControlMask;
            break;
          case Gdk.Key.Shift_L:
          case Gdk.Key.Shift_R:
            state &= ~Gdk.ModifierType.ShiftMask;
            break;
          case Gdk.Key.Alt_L:
          case Gdk.Key.Alt_R:
            state &= ~Gdk.ModifierType.Mod1Mask;
            break;
        }
        SetCursor (state);
      };
  
      DragDataGet += delegate (object o, DragDataGetArgs args) {
        string items = "file://" + DragSourcePath;
        if (Selection.Count > 0 && Selection.ContainsKey(DragSourcePath))
          items = GetSelectionData ();
        args.SelectionData.Set(args.SelectionData.Target, 8, System.Text.Encoding.UTF8.GetBytes(items));
        args.SelectionData.Text = items;
      };
  
      DragEnd += delegate {
        GetSelectionData ();
        Cancelled = true;
        dragInProgress = false;
        DragSourceEntry = null;
        DragSourcePath = null;
      };
  
      /** DESCTRUCTIVE */
      DragDataReceived += delegate (object sender, DragDataReceivedArgs e) {
        Cancelled = true;
        string targetPath = FindHit (Width, Height, e.X, e.Y, 8).Target.FullName;
        HandleSelectionData (e.SelectionData, e.Context.SuggestedAction, targetPath);
      };
  
      Gtk.Drag.DestSet (this, DestDefaults.All, target_table,
          Gdk.DragAction.Move
        | Gdk.DragAction.Copy
        | Gdk.DragAction.Ask
      );
  
      AddEvents((int)(
          Gdk.EventMask.ButtonPressMask
        | Gdk.EventMask.ButtonReleaseMask
        | Gdk.EventMask.ScrollMask
        | Gdk.EventMask.PointerMotionMask
        | Gdk.EventMask.EnterNotifyMask
        | Gdk.EventMask.KeyPressMask
        | Gdk.EventMask.KeyReleaseMask
        | Gdk.EventMask.LeaveNotifyMask
      ));
  
      ThreadStart ts = new ThreadStart (PreDrawCallback);
      Thread t = new Thread(ts);
      t.IsBackground = true;
      t.Start ();
  
      System.Timers.Timer t1 = new System.Timers.Timer(50);
      t1.Elapsed += new ElapsedEventHandler (CheckUpdates);
      System.Timers.Timer t2 = new System.Timers.Timer(1000);
      t2.Elapsed += new ElapsedEventHandler (LongMonitor);
      t1.Enabled = true;
      t2.Enabled = true;
      GLib.Timeout.Add (10, new GLib.TimeoutHandler(CheckRedraw));
      Helpers.StartupProfiler.Total ("Pre-drawing startup");
  
    }
  
    bool LimitedRedraw = false;
  
    bool CheckRedraw ()
    {
      if (PreDrawInProgress || NeedRedraw) {
        NeedRedraw = false;
        FSNeedRedraw = true;
        LimitedRedraw = false;
        QueueDraw ();
      } else if (LimitedRedraw) {
        LimitedRedraw = false;
        QueueDraw ();
      }
      return true;
    }
  
    void CheckUpdates (object source, ElapsedEventArgs e)
    {
      if (LastRedraw != FSCache.LastChange) {
        LastRedraw = FSCache.LastChange;
        RequestPreDraw ();
      }
    }
  
    void LongMonitor (object source, ElapsedEventArgs ev)
    {
      foreach (UnixDirectoryInfo e in Helpers.EntriesMaybe(CurrentDirPath)) {
        DateTime mtime = Helpers.LastChange(e.FullName);
        FSEntry f = FSCache.FastGet(e.FullName);
        if (f.LastFileChange != mtime) {
        Helpers.LogDebug ("LongMonitor invalidated {0}", e.FullName);
          f.LastFileChange = mtime;
          FSCache.Invalidate(e.FullName);
        }
      }
    }
  
  
    /* Files model */
  
    /** BLOCKING */
    public void SetCurrentDir (string dirname)
    {
      SetCurrentDir (dirname, true);
    }
    public void SetCurrentDir (string dirname, bool resetZoom)
    {
      Profiler p = new Profiler ();
      dirLatencyProfiler.Restart ();
      FirstFrameOfDir = true;
  
      if (dirname != Helpers.RootDir) dirname = dirname.TrimEnd(Helpers.DirSepC);
      UnixDirectoryInfo d = new UnixDirectoryInfo (dirname);
      string odp = CurrentDirPath;
      CurrentDirPath = d.FullName;
  
      if (odp != CurrentDirPath) {
        if (CurrentDirEntry != null && CurrentDirEntry.InProgress)
          FSCache.CancelTraversal ();
        CurrentDirEntry = FSCache.FastGet (CurrentDirPath);
        FSCache.Watch (CurrentDirPath);
      }
  
      FSNeedRedraw = true;
      if (resetZoom) ResetZoom ();
      UpdateLayout ();
      p.Time("SetCurrentDir");
    }
  
  
    /* Layout */
  
    /** BLOCKING */
    void UpdateLayout ()
    {
      RequestPreDraw ();
    }
  
    bool PreDrawInProgress = false;
    bool PreDrawRequested = false;
    bool clearTraversal = false;
    /** BLOCKING */
    void RequestPreDraw ()
    {
      /// Tell currently running PreDraw to stop.
      Renderer.CancelPreDraw();
      /// Tell PreDrawThread to start a new PreDraw after the current has stopped.
      PreDrawRequested = PreDrawInProgress = true;
    }
  
    /** ASYNC */
    void PreDrawCallback ()
    {
      Thread.CurrentThread.Priority = ThreadPriority.BelowNormal;
      while (true) {
        PreDrawInProgress = PreDrawInProgress || PreDrawRequested;
        if (PreDrawInProgress) {
          // even if PreDrawRequested is set to true here, we still do the predraw
          // (and CancelPreDraw only affects an already running PreDraw)
          PreDrawRequested = false;
          try {
            if (FSCache.Measurer != null && SizeField != null)
              if (!SizeField.Measurer.DependsOnTotals && FSCache.Measurer.DependsOnTotals)
                clearTraversal = true;
            FSCache.Measurer = SizeField.Measurer;
            FSCache.SortDirection = SortDirection;
            FSCache.Comparer = SortField.Comparer;
            if (!FSCache.Measurer.DependsOnTotals)
              FSCache.CancelTraversal ();
            if (clearTraversal) {
              clearTraversal = false;
              FSCache.ClearTraversalCache ();
            }
            FSCache.PruneCache (1);
  
            FSCache.CancelThumbnailing ();
            using (Context cr = new Context (PreDrawSurface)) {
              cr.IdentityMatrix ();
              Rectangle target = Transform (cr, Width, Height);
              cr.Scale (1, Zoomer.Z);
              cr.Translate (0.0, Zoomer.Y);
              PreDrawComplete = Renderer.PreDraw (CurrentDirEntry, cr, target, 0);
              if (PreDrawComplete) NeedRedraw = true;
            }
          } finally {
            PreDrawInProgress = !PreDrawComplete;
          }
        } else {
          Thread.Sleep (10);
        }
      }
    }
  
  
  
    /* Drawing */
  
    void DrawFrame (Context cr)
    {
      var fp = new Profiler ("DrawFrame");
      int w, h;
      GdkWindow.GetSize (out w, out h);
      fp.Time ("Window.GetSize");
      double x = dragX, y = dragY;
      bool sizeChanged = false;
      fp.Time ("GetPointer");
      if (InteractionProfiler.Watch.IsRunning)
        InteractionProfiler.Time ("From UI action to expose");
      if (Width != (uint)w || Height != (uint)h || CachedSurface == null) {
        if (CachedSurface != null) CachedSurface.Destroy ();
        CachedSurface = new ImageSurface(Format.ARGB32, w, h);
        sizeChanged = true;
        Width = (uint) w;
        Height = (uint) h;
        UpdateLayout ();
        fp.Time ("Recreate CachedSurface");
      }
      if (Cancelled) {
        ThrowFrames.Clear ();
        ThrowVelocity = 0;
      }
      if (ThrowVelocity != 0) {
        using ( Context ecr = new Context (EtcSurface) )
          PanBy (ecr, Width, Height, 0, ThrowVelocity);
        ThrowVelocity *= 0.98;
        if (Math.Abs(ThrowVelocity) < 1)
          ThrowVelocity = 0;
      }
      if (ZoomVelocity != 1) {
        using ( Context ecr = new Context (EtcSurface) )
          ZoomBy (ecr, Width, Height, x, y, ZoomVelocity);
        ZoomVelocity = Math.Pow(ZoomVelocity, 0.8);
        if (Math.Abs(1 - ZoomVelocity) < 0.001)
          ZoomVelocity = 1;
      }
      if (NeedZoomCheck)
        using ( Context ecr = new Context (EtcSurface) )
          CheckZoomNavigation(ecr, Width, Height);
      fp.Time ("Throw and zoom");
      if (sizeChanged || (!EffectInProgress && FSNeedRedraw)) {
        FSNeedRedraw = false;
        using (Context scr = new Context (CachedSurface)) {
          scr.Save ();
            scr.Operator = Operator.Source;
            scr.SetSourceRGBA (0,0,0,0);
            scr.Paint ();
          scr.Restore ();
          DrawMainView (scr, Width, Height);
          if (scr.Status != Status.Success)
            Helpers.LogError("Cairo error: {0}", scr.Status);
        }
        fp.Time ("FS Draw");
      }
      cr.Operator = Operator.Over;
      DrawBackground (cr, Width, Height);
      fp.Time ("DrawBackground");
      if (ControlLineVisible && panning) {
        cr.Save ();
          cr.Color = Renderer.DirectoryFGColor;
          cr.Rectangle (0, dragY, Width, 1);
          cr.Fill ();
        cr.Restore ();
      }
      using (SurfacePattern p = new SurfacePattern (CachedSurface)) {
        cr.Source = p;
        cr.Paint ();
        cr.Operator = Operator.Over;
        fp.Time ("Composite");
        if (DrawEffects (cr, Width, Height)) {
          LimitedRedraw = true;
          fp.Time ("Effects");
        }
      }
      if (InteractionProfiler.Watch.IsRunning) {
        InteractionProfiler.Total ("Interaction latency");
        InteractionProfiler.Stop ();
        InteractionProfiler.Reset ();
        InteractionProfiler.TotalElapsed = 0;
      }
  //     fp.Total ("Frame");
    }
  
    /** FAST */
    Rectangle Transform (Context cr, uint width, uint height)
    {
      double boxHeight = Math.Max(1, height-FilesMarginTop-FilesMarginBottom);
      double boxWidth = Math.Max(1, width-FilesMarginLeft-FilesMarginRight);
      cr.Translate(FilesMarginLeft, FilesMarginTop);
      double x = cr.Matrix.X0;
      double y = cr.Matrix.Y0;
      double w =  boxWidth * cr.Matrix.Xx;
      double h = boxHeight * cr.Matrix.Yy;
      cr.Rectangle (0, 0, boxWidth, boxHeight);
      cr.Clip ();
      cr.Scale (boxHeight, boxHeight);
      return new Rectangle (x,y,w,h);
    }
  
    /** FAST */
    void DrawClear (Context cr, uint width, uint height)
    {
      cr.Save ();
      Color co = Renderer.BackgroundColor;
      if (UseRgbaVisuals)
        co.A = 1;
      cr.Color = co;
      cr.Operator = Operator.Source;
      cr.Rectangle (0,0, width, height);
      cr.Fill ();
      cr.Restore ();
    }
  
    double FirstProgress = 0;
    double LastProgress = 0;
    double CylinderRotation = 0;
    /** FAST */
    void DrawBackground (Context cr, uint width, uint height)
    {
      cr.Save ();
        DrawClear (cr, width, height);
        double t = DateTime.Now.ToFileTime() / 1e7;
        cr.Save ();
          Color ca = Renderer.DirectoryFGColor;
          ca.A = 0.3;
          cr.Color = ca;
          cr.LineWidth = 0.5;
          cr.Translate (270, -50);
          cr.Rotate (0.15);
          for (double y=0; y<6; y++) {
          for (double i=0; i<6-y; i++) {
            cr.Save ();
              double hr = 45;
              double iscale = Math.Sin(-i/20 * Math.PI*2 + CylinderRotation);
              double xscale = Math.Cos(-i/20 * Math.PI*2 + 0.1 + CylinderRotation);
              cr.Translate (iscale * hr * 3, -100 + y*hr*(2*1.732) + hr*(i%2)*1.732);
              hr = 45;
              cr.Scale (xscale, 1);
              cr.NewPath ();
              cr.MoveTo (0, -hr);
              cr.LineTo (0.866*hr, -0.5*hr);
              cr.LineTo (0.866*hr, 0.5*hr);
              cr.LineTo (0, hr);
              cr.LineTo (-0.866*hr, 0.5*hr);
              cr.LineTo (-0.866*hr, -0.5*hr);
              cr.ClosePath ();
              cr.Color = ca;
              cr.Stroke ();
              cr.Color = Renderer.SocketColor;
              cr.Translate (0.75 * -0.866*hr, 0.75 * -0.5*hr);
              double x2 = cr.Matrix.X0, y2 = cr.Matrix.Y0;
              cr.IdentityMatrix ();
              cr.Arc (x2,y2, 1, 0, Math.PI*2);
              cr.Fill ();
            cr.Restore ();
          }
          }
          CylinderRotation += 0.02;
        cr.Restore ();
        if (CurrentDirEntry.InProgress || (t - LastProgress < 3)) {
          if (FirstProgress == 0) { FirstProgress = LastProgress = t; }
          if (CurrentDirEntry.InProgress) LastProgress = t;
          double opacity = Math.Min(3, t-FirstProgress) - Math.Max(0, t-LastProgress);
          t = (t * 0.1) % Math.PI*2;
          Color c = Renderer.RegularFileColor;
          c.A = 0.1*opacity;
          cr.Color = c;
          cr.LineWidth = 1;
          double n = 6;
          double af = Math.PI*2/n;
          double r = 400*(4.4+0.3*cosScale(opacity/3));
          for (double i=0; i<n; i++) {
            cr.Arc (-400*4, 1000/4, r, t+i*af, t+(i+0.7)*af);
            cr.Stroke ();
          }
          for (double i=0; i<n; i++) {
            cr.Arc (-400*4, 1000/4, r+5, -t+i*af, -t+(i+0.7)*af);
            cr.Stroke ();
          }
          if (CurrentDirEntry.InProgress) {
            cr.NewPath ();
              // find FSCache.LastTraversed [or ancestor] y position from model
              // draw line there
            cr.NewPath ();
          }
          LimitedRedraw = true;
        } else {
          FirstProgress = 0;
          LastProgress = 0;
        }
      cr.Restore ();
    }
  
    double cosScale (double n) {
      return 0.5*(1-Math.Cos(n*Math.PI));
    }
  
    bool DrawEffects  (Context cr, uint w, uint h)
    {
      if (!SillyFlare) return false;
      if (FlareGradient == null) {
        FGRadius = Helpers.ImageWidth(FlareGradientImage);
        FlareGradient = Helpers.RadialGradientFromImage(FlareGradientImage);
        BlackGradient = new RadialGradient(0,0,0, 0,0,FGRadius);
        BlackGradient.AddColorStop(0, new Color(0,0,0,1));
        BlackGradient.AddColorStop(1, new Color(0,0,0,0));
        FlareSpike = new ImageSurface(FlareSpikeImage);
        RainbowSprite = new ImageSurface(RainbowSpriteImage);
      }
      cr.Save ();
  //       double t = DateTime.Now.ToFileTime() / 1e7;
        double dx = flareTargetX - flareX;
        double dy = flareTargetY - flareY;
        flareX += dx / 20;
        flareY += dy / 20;
        double s = Math.Min(1, Math.Max(0.02, 0.35 / (1 + 0.002*(dx*dx + dy*dy))));
        if (s < 0.03)
          s *= 1 + rng.NextDouble();
        cr.Translate(flareX, flareY);
        cr.Save ();
          cr.Scale (s, s);
  /*        cr.Arc(0, 0, FGRadius, 0, Math.PI * 2);
          cr.Source = BlackGradient;
          cr.Operator = Operator.Over;*/
          cr.FillPreserve ();
          cr.Source = FlareGradient;
          cr.Operator = Operator.Add;
          cr.Fill ();
        cr.Restore ();
        cr.Save ();
          cr.Scale (s, s);
          cr.Operator = Operator.Add;
          using (SurfacePattern p = new SurfacePattern(RainbowSprite)) {
            cr.Save ();
            cr.Translate (10, -RainbowSprite.Height/2);
            cr.Rectangle (0, 0, RainbowSprite.Width, RainbowSprite.Height);
            cr.Source = p;
            cr.Fill ();
            cr.Restore ();
            cr.Save ();
            cr.Scale(-1, 1);
            cr.Translate (10, -RainbowSprite.Height/2);
            cr.Rectangle (0, 0, RainbowSprite.Width, RainbowSprite.Height);
            cr.Source = p;
            cr.Fill ();
            cr.Restore ();
          }
        cr.Restore ();
        cr.Save ();
          cr.Scale (Math.Sqrt(s), Math.Sqrt(s));
          using (SurfacePattern p = new SurfacePattern(FlareSpike)) {
            cr.Translate (-FlareSpike.Width/2.0, -FlareSpike.Height/2.0);
            cr.Rectangle (0, 0, FlareSpike.Width, FlareSpike.Height);
            cr.Operator = Operator.Add;
            cr.Source = p;
            cr.Fill ();
          }
        cr.Restore ();
      cr.Restore ();
      if (dx*dx < 1 && dy*dy < 1) return false;
      return true;
    }
  
    Random rng = new Random ();
  
    double flareX = 200;
    double flareY = -200;
  
    double flareTargetX = 200;
    double flareTargetY = -200;
  
    bool SillyFlare = false;
  
    string FlareGradientImage = "res/flare_gradient.png";
    string FlareSpikeImage = "res/flare_spike.png";
    string RainbowSpriteImage = "res/rainbow_sprite.png";
    RadialGradient FlareGradient = null;
    RadialGradient BlackGradient;
    int FGRadius;
    ImageSurface FlareSpike;
    ImageSurface RainbowSprite;
  
  
  
    /* Draw main view */
  
    /** BLOCKING */
    void DrawMainView (Context cr, uint width, uint height)
    {
      cr.Save ();
        cr.NewPath ();
        cr.IdentityMatrix ();
        DrawToolbars (cr, width, height);
        cr.NewPath ();
        cr.IdentityMatrix ();
        Rectangle targetBox = Transform (cr, width, height);
        DrawCurrentDir(cr, targetBox);
      cr.Restore ();
  
      dirLatencyProfiler.Stop ();
      if (FirstFrameOfDir) {
        dirLatencyProfiler.Time ("Directory latency");
        FirstFrameOfDir = false;
      }
      if (Helpers.StartupProfiler.Watch.IsRunning) {
        Helpers.StartupProfiler.Time ("Draw complete");
        Helpers.StartupProfiler.Total ("Startup complete");
        Helpers.StartupProfiler.Stop ();
        if (QuitAfterFirstFrame) Application.Quit ();
      }
    }
  
    /** FAST */
    void DrawToolbars (Context cr, uint width, uint height)
    {
      Profiler p = new Profiler ();
      cr.Save ();
        cr.Translate (BreadcrumbMarginLeft, BreadcrumbMarginTop);
        DrawBreadcrumb (cr, width);
        cr.Translate (0, ToolbarY);
        DrawSortBar (cr);
        DrawSizeBar (cr);
      cr.Restore ();
      p.Total ("DrawToolbars");
    }
  
    /** BLOCKING */
    void DrawCurrentDir (Context cr, Rectangle targetBox)
    {
      Profiler p = new Profiler ();
      uint c;
      cr.Save ();
        cr.Scale (1, Zoomer.Z);
        cr.Translate (0.0, Zoomer.Y);
        Renderer.FileNameFontFamily = FileNameFontFamily;
        Renderer.FileInfoFontFamily = FileInfoFontFamily;
        c = Renderer.Draw(CurrentDirEntry, Prefixes, Selection, cr, targetBox);
      cr.Restore ();
      p.Time (String.Format("DrawCurrentDir: {0} entries", c));
    }
  
    string rootChar = "/";
    string dirSep = "/";
  
    /** FAST */
    void DrawBreadcrumb (Context cr, uint width)
    {
      cr.NewPath ();
      cr.MoveTo (0.0, 0.0);
      Profiler p = new Profiler ();
      p.Time("In breadcrumb");
      TextExtents te = Helpers.GetTextExtents (cr, BreadcrumbFontFamily, BreadcrumbFontSize, String.Join(dirSep, CurrentDirPath.Split(Helpers.DirSepC)) + dirSep);
      p.Time("GetTextExtents");
      cr.Color = Renderer.DirectoryFGColor;
      cr.Save ();
        double areaWidth = width-BreadcrumbMarginLeft-BreadcrumbMarginRight;
        cr.Rectangle (0,0,areaWidth, te.Height);
        cr.Clip ();
        cr.Translate (Math.Min(0,areaWidth-te.Width), 0);
        cr.MoveTo (0.0, 0.0);
        if (CurrentDirPath == Helpers.RootDir) {
          Helpers.DrawText (cr, BreadcrumbFontFamily, BreadcrumbFontSize, rootChar);
        } else {
      p.Time("start DrawText");
          foreach (string s in CurrentDirPath.Split(Helpers.DirSepC)) {
            Helpers.DrawText (cr, BreadcrumbFontFamily, BreadcrumbFontSize, s == "" ? rootChar : (s+dirSep));
          }
      p.Time("DrawText");
        }
      cr.Restore ();
    }
  
    /** FAST */
    void DrawSortBar (Context cr)
    {
      cr.NewPath ();
      cr.MoveTo (0.0, 0.0);
      cr.Color = ActiveColor;
      cr.RelMoveTo ( 0.0, ToolbarLabelFontSize * 0.4 );
      Helpers.DrawText (cr, ToolbarTitleFontFamily, ToolbarTitleFontSize, SortLabel);
      cr.RelMoveTo ( 0.0, ToolbarLabelFontSize * -0.4 );
      foreach (SortHandler sf in SortFields) {
        cr.Color = (SortField == sf) ? ActiveColor : InActiveColor;
        Helpers.DrawText (cr, ToolbarLabelFontFamily, ToolbarLabelFontSize, sf.Name);
        Helpers.DrawText (cr, ToolbarLabelFontFamily, ToolbarLabelFontSize, " ");
      }
    }
  
    /** FAST */
    void DrawSizeBar (Context cr)
    {
      cr.Color = ActiveColor;
      cr.RelMoveTo ( 0.0, ToolbarLabelFontSize * 0.4 );
      Helpers.DrawText (cr, ToolbarTitleFontFamily, ToolbarTitleFontSize, SizeLabel);
      cr.RelMoveTo ( 0.0, ToolbarLabelFontSize * -0.4 );
      foreach (SizeHandler sf in SizeFields) {
        cr.Color = (SizeField == sf) ? ActiveColor : InActiveColor;
        Helpers.DrawText (cr, ToolbarLabelFontFamily, ToolbarLabelFontSize, sf.Name);
        Helpers.DrawText (cr, ToolbarLabelFontFamily, ToolbarLabelFontSize, " ");
      }
    }
  
  
  
    /* Click handling */
  
    /** BLOCKING */
    void Click (Context cr, uint width, uint height, double x, double y)
    {
      cr.Save ();
        cr.Translate (BreadcrumbMarginLeft, BreadcrumbMarginTop);
        cr.Operator = Operator.Dest;
        if (ClickBreadcrumb (cr, width, x, y)) {
          cr.Restore ();
          return;
        }
        cr.Translate (0, ToolbarY);
        if (
          ClickSortBar (cr, x, y) ||
          ClickSizeBar (cr, x, y)
        ) {
          cr.Restore ();
          return;
        }
      cr.Restore ();
      cr.Save();
        ClickCurrentDir(cr, width, height, x, y);
      cr.Restore ();
    }
  
    FSEntry spanStart = null;
    FSEntry spanEnd = null;
  
    void ToggleSpan (FSEntry start, FSEntry end)
    {
      if (start.ParentDir == end.ParentDir) {
        FSEntry[] entries = end.ParentDir.Entries.ToArray ();
        int startIdx = Array.IndexOf(entries, start);
        int endIdx = Array.IndexOf(entries, end);
        if (startIdx > -1 && endIdx > -1) {
          int i = Math.Min (startIdx, endIdx);
          int j = Math.Max (startIdx, endIdx);
          for (;i<=j;i++) {
            if (i != startIdx)
              ToggleSelection(entries[i].FullName);
          }
        }
        NeedRedraw = true;
      }
    }
  
    public void ToggleSelection (string path)
    {
      if (Selection.ContainsKey(path)) Selection.Remove(path);
      else Selection[path] = true;
      NeedRedraw = true;
    }
  
    public void ClearSelection ()
    {
      Selection.Clear ();
      UpdateLayout ();
    }
  
    /** BLOCKING */
    void ClickCurrentDir (Context cr, uint width, uint height, double x, double y)
    {
      Rectangle box = Transform (cr, width, height);
      if (x < box.X || x > box.X+box.Width || y < box.Y || y > box.Y+box.Height)
        return;
      cr.Scale (1, Zoomer.Z);
      cr.Translate (0.0, Zoomer.Y);
      List<ClickHit> hits = Renderer.Click (CurrentDirEntry, Prefixes, cr, box, x, y);
      foreach (ClickHit c in hits) {
        if (c.Height < 15.9) {
          if (c.Target.ParentDir == CurrentDirEntry) {
            double nz = (c.Target.IsDirectory ? 24 : 18) / c.Height;
            // Helpers.LogDebug("ZoomIn {0}x", nz);
            cr.Save ();
              cr.IdentityMatrix ();
              ZoomBy(cr, width, height, x, y, nz);
            cr.Restore ();
            break;
          }
        } else if (DoubleClick) {
          break;
        } else {
          if (AltKeyDown) {
            ClearSelection ();
          } else if (CtrlKeyDown) {
            ToggleSelection(c.Target.FullName);
            spanStart = c.Target;
            spanEnd = null;
          } else if (ShiftKeyDown) {
            if (spanStart != null) {
              if (spanEnd != null) {
                ToggleSpan(spanStart, spanEnd);
              }
              spanEnd = c.Target;
              ToggleSpan(spanStart, spanEnd);
            } else {
              ToggleSelection(c.Target.FullName);
              spanStart = c.Target;
              spanEnd = null;
            }
          } else {
            if (c.Target.IsDirectory) {
              // Helpers.LogDebug("Navigate {0}", c.Target.FullName);
              SetCurrentDir (c.Target.FullName);
            } else {
              // Helpers.LogDebug("Open {0}", c.Target.FullName);
              OpenFile(c.Target.FullName);
            }
          }
          UpdateLayout ();
          break;
        }
      }
    }
  
    /** FAST */
    bool ClickBreadcrumb (Context cr, uint width, double x, double y)
    {
      if (CurrentDirPath == Helpers.RootDir) return false;
      TextExtents te1 = Helpers.GetTextExtents (cr, BreadcrumbFontFamily, BreadcrumbFontSize, String.Join(dirSep, CurrentDirPath.Split(Helpers.DirSepC)) + dirSep);
      cr.Save ();
        double areaWidth = width-BreadcrumbMarginLeft-BreadcrumbMarginRight;
        cr.Rectangle (0,0,areaWidth, te1.Height);
        cr.Clip ();
        cr.Translate (Math.Min(0,areaWidth-te1.Width), -BreadcrumbMarginTop);
        if (areaWidth - te1.Width >= 0)
          cr.Translate(-(BreadcrumbMarginLeft+1), 0);
        cr.MoveTo (0.0, 0.0);
        int hitIndex = 0;
        string[] segments = CurrentDirPath.Split(Helpers.DirSepC);
        foreach (string s in segments) {
          string name = (s == "") ? rootChar : s+dirSep;
          TextExtents te = Helpers.GetTextExtents (cr, BreadcrumbFontFamily, BreadcrumbFontSize, name);
          te.Height += BreadcrumbMarginTop;
          if (s == "" && (areaWidth - te1.Width >= 0))
            te.Width += BreadcrumbMarginLeft+1;
          if (Helpers.CheckTextExtents(cr, te, x, y)) {
            string newDir = String.Join(Helpers.DirSepS, segments, 0, hitIndex+1);
            if (newDir == "") newDir = Helpers.RootDir;
            if (newDir != CurrentDirPath) {
              SetCurrentDir (newDir);
            } else {
              ResetZoom ();
              UpdateLayout ();
            }
            cr.Restore ();
            return true;
          }
          cr.RelMoveTo( te.Width, 0 );
          hitIndex += 1;
        }
        cr.IdentityMatrix ();
        cr.Rectangle (0,-1,width,2);
        if (cr.InFill(x,y)) {
          ResetZoom ();
          UpdateLayout ();
        }
      cr.Restore ();
      return false;
    }
  
    /** FAST */
    bool ClickSortBar (Context cr, double x, double y)
    {
      cr.NewPath ();
      cr.MoveTo (0.0, 0.0);
      TextExtents te;
      cr.Color = ActiveColor;
      cr.RelMoveTo ( 0.0, ToolbarLabelFontSize * 0.4 );
      Helpers.DrawText (cr, ToolbarTitleFontFamily, ToolbarTitleFontSize, SortLabel);
      cr.RelMoveTo ( 0.0, ToolbarLabelFontSize * -0.4 );
      foreach (SortHandler sf in SortFields) {
        cr.Color = (SortField == sf) ? ActiveColor : InActiveColor;
        te = Helpers.GetTextExtents (
          cr, ToolbarLabelFontFamily, ToolbarLabelFontSize, sf.Name);
        if (Helpers.CheckTextExtents(cr, te, x, y)) {
          SortField = sf;
          ResetZoom ();
          UpdateLayout ();
          return true;
        }
        Helpers.DrawText (cr, ToolbarLabelFontFamily, ToolbarLabelFontSize, sf.Name);
        Helpers.DrawText (cr, ToolbarLabelFontFamily, ToolbarLabelFontSize, " ");
      }
      return false;
    }
  
    /** FAST */
    bool ClickSizeBar (Context cr, double x, double y)
    {
      TextExtents te;
      cr.Color = ActiveColor;
      cr.RelMoveTo ( 0.0, ToolbarLabelFontSize * 0.4 );
      Helpers.DrawText (cr, ToolbarTitleFontFamily, ToolbarTitleFontSize, SizeLabel);
      cr.RelMoveTo ( 0.0, ToolbarLabelFontSize * -0.4 );
      foreach (SizeHandler sf in SizeFields) {
        cr.Color = (SizeField == sf) ? ActiveColor : InActiveColor;
        te = Helpers.GetTextExtents (
          cr, ToolbarLabelFontFamily, ToolbarLabelFontSize, sf.Name);
        if (Helpers.CheckTextExtents(cr, te, x, y)) {
          SizeField = sf;
          ResetZoom ();
          UpdateLayout ();
          return true;
        }
        Helpers.DrawText (cr, ToolbarLabelFontFamily, ToolbarLabelFontSize, sf.Name);
        Helpers.DrawText (cr, ToolbarLabelFontFamily, ToolbarLabelFontSize, " ");
      }
      Helpers.DrawText (cr, ToolbarLabelFontFamily, ToolbarLabelFontSize, " ");
      return false;
    }
  
    /** BLOCKING */
    public List<ClickHit> FindHits (uint width, uint height, double x, double y)
    {
      Profiler p = new Profiler ("FindHits", 10);
      List<ClickHit> hits;
      using (Context cr = new Context (CachedSurface)) {
        cr.IdentityMatrix ();
        cr.Save();
          Rectangle box = Transform (cr, width, height);
          cr.Scale (1, Zoomer.Z);
          cr.Translate (0.0, Zoomer.Y);
          hits = Renderer.Click (CurrentDirEntry, Prefixes, cr, box, x, y);
          hits.Add (new ClickHit(CurrentDirEntry, cr.Matrix.Yy));
        cr.Restore ();
      }
      p.Time ("Found {0} hits", hits.Count);
      return hits;
    }
  
    /** BLOCKING */
    public ClickHit FindHit (uint width, uint height, double x, double y, double minHeight)
    {
      List<ClickHit> hits = FindHits(width, height, x, y);
      ClickHit ch = hits[hits.Count-1];
      foreach (ClickHit c in hits) {
        if (c.Height > minHeight) {
          ch = c;
          break;
        }
      }
      return ch;
    }
  
  
    /* Context menu */
  
  
    /** BLOCKING */
    void ContextClick (uint width, uint height, double x, double y)
    {
      if (ContextMenu != null) ContextMenu.Dispose ();
      ContextMenu = new FilezooContextMenu (this, FindHit(width, height, x, y, 8));
      ContextMenu.ShowAll ();
      ContextMenu.Popup ();
    }
  
  
    /* Zooming and panning */
  
    /** FAST */
    void ResetZoom () {
      Zoomer.ResetZoom ();
      Zoomer.SetZoom (0.0, Renderer.DefaultPan, Renderer.DefaultZoom);
      ZoomVelocity = 1;
      ThrowVelocity = 0;
      ThrowFrames.Clear ();
    }
  
    bool NeedZoomCheck = false;
  
    /** BLOCKING */
    void ZoomBy
    (Context cr, uint width, uint height, double x, double y, double factor)
    {
      double xr = x, yr = y, nz = Zoomer.Z * factor;
      if (CurrentDirPath == Helpers.RootDir && nz < 1) nz = 1;
      cr.Save ();
        Transform (cr, width, height);
        cr.InverseTransformPoint(ref xr, ref yr);
        double npy = (yr / nz) - (yr / Zoomer.Z) + Zoomer.Y;
        Zoomer.SetZoom (0.0, npy, nz);
      cr.Restore ();
      NeedZoomCheck = true;
      UpdateLayout();
    }
  
    /** BLOCKING */
    void ZoomToward (Context cr, uint width, uint height, double x, double y) {
      if (ZoomVelocity >= 1)
        ZoomVelocity *= Math.Pow(ZoomInSpeed, 0.3);
      else
        ZoomVelocity = 1;
      NeedRedraw = true;
    }
  
    /** BLOCKING */
    void ZoomAway (Context cr, uint width, uint height, double x, double y) {
      if (ZoomVelocity <= 1)
        ZoomVelocity *= Math.Pow(1 / ZoomOutSpeed, 0.3);
      else
        ZoomVelocity = 1;
      NeedRedraw = true;
    }
  
    /** BLOCKING */
    void PanBy (Context cr, uint width, uint height, double dx, double dy)
    {
      double xr = dx, yr = dy;
      cr.Save ();
        Transform (cr, width, height);
        cr.InverseTransformDistance(ref xr, ref yr);
        Zoomer.Y += yr / Zoomer.Z;
      cr.Restore ();
      NeedZoomCheck = true;
      UpdateLayout();
    }
  
    /** BLOCKING */
    void CheckZoomNavigation (Context cr, uint width, uint height)
    {
      Profiler p = new Profiler ("CheckZoomNavigation");
      cr.Save ();
        Rectangle r = Transform (cr, width, height);
        cr.Scale (1, Zoomer.Z);
        cr.Translate (0.0, Zoomer.Y);
        Covering c = Renderer.FindCovering(CurrentDirEntry, cr, r, 0);
        if (c.Directory.FullName != CurrentDirPath) {
          SetCurrentDir(c.Directory.FullName, false);
          Zoomer.SetZoom (0.0, c.Pan, c.Zoom);
        }
      cr.Restore ();
      p.Time ("Checked");
    }
  
  
    /* Event handlers */
  
    double ZoomVelocity = 1;
  
    double ThrowVelocity = 0;
    List<ThrowFrame> ThrowFrames = new List<ThrowFrame> ();
    struct ThrowFrame {
      public double X, Y, Time;
      public ThrowFrame (double x, double y) {
        X=x; Y=y;
        Time = DateTime.Now.ToFileTime () / 1e7;
      }
    }
  
    bool ClickCancelled = false;
  
    /** FAST */
    protected override bool OnButtonPressEvent (Gdk.EventButton e)
    {
      Profiler p = new Profiler ("OnButtonPressEvent", 10);
      GrabFocus ();
      SetCursor (e.State);
      p.Time ("SetCursor");
      dragStartX = dragX = e.X;
      dragStartY = dragY = e.Y;
      ZoomVelocity = 1;
      ThrowFrames.Clear ();
      ClickCancelled = Cancelled = false;
      if (ThrowVelocity != 0)
        ClickCancelled = true;
      ThrowVelocity = 0;
      if (e.Button == 1 || e.Button == 2)
        ThrowFrames.Add (new ThrowFrame(e.X, e.Y));
      dragging = false;
      if (e.Button == 3) {
        int w, h;
        e.Window.GetSize (out w, out h);
        ContextClick ((uint)w, (uint)h, e.X, e.Y);
      } else if (e.Button == 1 && !dragging) {
        DoubleClick = (e.Type == Gdk.EventType.TwoButtonPress);
      }
      if (e.Button == 1) {
        int w, h;
        e.Window.GetSize (out w, out h);
        DragSourceEntry = FindHit((uint)w, (uint)h, e.X, e.Y, 8).Target;
        DragSourcePath = DragSourceEntry.FullName;
      }
      p.Time ("Handled");
      return true;
    }
  
    bool AltKeyDown = false;
    bool CtrlKeyDown = false;
    bool ShiftKeyDown = false;
    bool DoubleClick = false;
  
    DateTime LastMotionTime = DateTime.Now;
  
    /** BLOCKING */
    protected override bool OnButtonReleaseEvent (Gdk.EventButton e)
    {
      Profiler p = new Profiler ("OnButtonReleaseEvent", 10);
      GrabFocus ();
      SetCursor (e.State);
      p.Time ("SetCursor");
      ZoomVelocity = 1;
      if (Cancelled) {
        Cancelled = DoubleClick = false;
      } else {
        if (e.Button == 1 && !dragging && !ClickCancelled) {
        InteractionProfiler.Start ();
          int w, h;
          e.Window.GetSize (out w, out h);
          AltKeyDown = (e.State & Gdk.ModifierType.Mod1Mask) == Gdk.ModifierType.Mod1Mask;
          CtrlKeyDown = (e.State & Gdk.ModifierType.ControlMask) == Gdk.ModifierType.ControlMask;
          ShiftKeyDown = (e.State & Gdk.ModifierType.ShiftMask) == Gdk.ModifierType.ShiftMask;
          if (AltKeyDown)
            ClearSelection ();
          using (Context scr = new Context (CachedSurface)) {
            scr.IdentityMatrix ();
            Click (scr, (uint)w, (uint)h, e.X, e.Y);
          }
          DoubleClick = false;
        }
        if (e.Button == 1 && ThrowFrames.Count > 0) {
          ThrowVelocity = 0;
          if (DateTime.Now.Subtract(LastMotionTime).TotalMilliseconds < 100) {
            ThrowFrames.Add (new ThrowFrame(e.X, e.Y));
            int len = Math.Min(10, ThrowFrames.Count-1);
            double vy = 0;
            for (int i=ThrowFrames.Count-len; i<ThrowFrames.Count; i++) {
              vy += ThrowFrames[i].Y - ThrowFrames[i-1].Y;
            }
            vy /= len;
            // Helpers.LogDebug("ThrowFrames.Count: {0}, vy: {1}", ThrowFrames.Count, vy);
            if (Math.Abs(vy) > 5) {
              ThrowVelocity = vy*2;
              NeedRedraw = true;
            }
          }
          ThrowFrames.Clear ();
        }
      }
      if (e.Button == 1) {
        if (!dragInProgress) {
          DragSourceEntry = null;
          DragSourcePath = null;
        }
        dragInProgress = false;
      }
      if (e.Button == 2) { ControlLineVisible = false; NeedRedraw = true; }
      dragging = false;
      panning = panning && dragInProgress;
      p.Time ("Handled");
      return true;
    }
  
    bool panning = false;
    bool dragInProgress = false;
    bool ControlLineVisible = false;
  
    /** FAST */
    protected override bool OnMotionNotifyEvent (Gdk.EventMotion e)
    {
      Profiler p = new Profiler ("OnMotionNotifyEvent", 10);
      SetCursor (e.State);
      p.Time("SetCursor");
      bool left = (e.State & Gdk.ModifierType.Button1Mask) == Gdk.ModifierType.Button1Mask;
      bool middle = (e.State & Gdk.ModifierType.Button2Mask) == Gdk.ModifierType.Button2Mask;
  
      if (!left && !middle) panning = dragging = false;
  
      if (!Cancelled) {
        if (left || middle) {
          LastMotionTime = DateTime.Now;
          ThrowFrames.Add (new ThrowFrame(e.X, e.Y));
        }
        if (middle) {
          dragging = true;
          panning = true;
        }
        if (left) {
          bool ctrl = (e.State & Gdk.ModifierType.ControlMask) == Gdk.ModifierType.ControlMask;
          dragging = dragging || ((Math.Abs(dragX - dragStartX) + Math.Abs(dragY - dragStartY)) > 4);
          panning = panning || !ctrl || (DragSourceEntry == CurrentDirEntry);
          if (!dragInProgress && !panning && dragging) {
            Gdk.DragAction action = Gdk.DragAction.Move;
            if ((e.State & Gdk.ModifierType.ShiftMask) == Gdk.ModifierType.ShiftMask)
              action = Gdk.DragAction.Copy;
            if ((e.State & Gdk.ModifierType.Mod1Mask) == Gdk.ModifierType.Mod1Mask)
              action = Gdk.DragAction.Ask;
            dragInProgress = true;
            Drag.Begin (this, new TargetList(targets), action, 1, e);
            SetCursor (e.State);
            Cancelled = true;
            ThrowVelocity = 0;
            ThrowFrames.Clear ();
          }
        }
        if (panning) {
          InteractionProfiler.Restart ();
          double dx = e.X - dragX;
          double dy = e.Y - dragY;
          using ( Context cr = new Context (EtcSurface) ) {
            if (middle) {
              ControlLineVisible = true;
              double z = Math.Pow((dx < 0 ? ZoomInSpeed : (1 / ZoomOutSpeed)), (Math.Abs(dx) / 50));
              if (Height - e.Y < 50)
                ThrowVelocity = -(50 - (Height - e.Y)) / 2;
              else if (e.Y < 50 + FilesMarginTop)
                ThrowVelocity = (50 + FilesMarginTop - e.Y) / 2;
              else
                ThrowVelocity = 0;
  /*            if (Width - e.X < 50)
                ZoomVelocity = Math.Pow((1/ZoomOutSpeed), (50 - (Width - e.X)) / 250);
              else if (e.X < 50)
  
gt:               ZoomVelocity = Math.Pow(ZoomInSpeed, (50 - e.X) / 250);