SyncCoordinatesAspect.java

/*
** Module   : SyncCoordinatesAspect.java
** Abstract : An aspect which intercepts assignment of coordinate fields.
**
** Copyright (c) 2014-2021, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------Description----------------------------------
** 001 HC  20141207 Created initial version.
** 002 HC  20150102 Fixed handling of uninitialized values for the coordinate fields.
** 003 HC  20150228 Referenced objects stored in local context.
** 004 HC  20150405 Refactoring of config coordinate fields.
** 005 HC  20150508 Improvements to GUI window scrolling support.
** 006 HC  20150513 Javadocs improved.
** 007 CA  20150722 Added a new exclusion in afterSetWidthChars: LabelConfig.setDefaults is called
**                  only from c'tors.
** 008 HC  20150803 Improved runtime support for WINDOW size attributes.
** 009 HC  20151110 Added client-side size fields.
** 010 HC  20160203 Improved runtime support of FRAME:VIRTUAL* attributes.
** 011 EVL 20160223 Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
** 012 HC  20160308 Fixed coordinate conversion of client size config fields. 
** 013 HC  20171102 Implemented image load size and offset, plus related changes.
** 014 EVL 20181112 Fixed incorrect transformation code for image size.
** 015 VVT 20210806 Added missing support for ButtonImageDefinition fields: offsetX, offsetCol,
**                  offsetY and offsetRow
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero 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 Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
** 
** Additional terms under GNU Affero GPL version 3 section 7:
** 
**   Under Section 7 of the GNU Affero GPL version 3, the following additional
**   terms apply to the works covered under the License.  These additional terms
**   are non-permissive additional terms allowed under Section 7 of the GNU
**   Affero GPL version 3 and may not be removed by you.
** 
**   0. Attribution Requirement.
** 
**     You must preserve all legal notices or author attributions in the covered
**     work or Appropriate Legal Notices displayed by works containing the covered
**     work.  You may not remove from the covered work any author or developer
**     credit already included within the covered work.
** 
**   1. No License To Use Trademarks.
** 
**     This license does not grant any license or rights to use the trademarks
**     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
**     of Golden Code Development Corporation. You are not authorized to use the
**     name Golden Code, FWD, or the names of any author or contributor, for
**     publicity purposes without written authorization.
** 
**   2. No Misrepresentation of Affiliation.
** 
**     You may not represent yourself as Golden Code Development Corporation or FWD.
** 
**     You may not represent yourself for publicity purposes as associated with
**     Golden Code Development Corporation, FWD, or any author or contributor to
**     the covered work, without written authorization.
** 
**   3. No Misrepresentation of Source or Origin.
** 
**     You may not represent the covered work as solely your work.  All modified
**     versions of the covered work must be marked in a reasonable way to make it
**     clear that the modified work is not originating from Golden Code Development
**     Corporation or FWD.  All modified versions must contain the notices of
**     attribution required in this license.
*/
package com.goldencode.p2j.aspects.ui;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;

import com.goldencode.p2j.net.SessionManager;
import com.goldencode.p2j.security.ContextLocal;
import com.goldencode.p2j.ui.*;
import com.goldencode.p2j.ui.chui.ThinClient;

/**
 * The aspect implements synchronization of linked coordinate
 * attributes like WIDTH-CHARS/WIDTH-PIXELS, COLUMN/X, etc. by
 * intercepting the respective public fields from
 * {@code com.goldencode.p2j.ui.*Config} classes. Also all assignments
 * of coordinate values to the config coordinate fields are scaled
 * through {@link Coordinate#scale(double)}.
 * <p>
 * The aspect intercepts both server and client.
 * <p>
 * During char-&gt;pixel conversion the converted value must be rounded to 
 * the nearest pixel. This also means that the original character value
 * doesn't correspond to its pixel representation. This is why another 
 * conversion from pixel to char is performed and the original character 
 * value set to the config field may change! 
 */
@Aspect
public class SyncCoordinatesAspect
{
   /** Storage of context-local state variables. */
   private ContextLocal<WorkArea> contextData = new ContextLocal<WorkArea>()
   {
      @Override
      protected WorkArea initialValue()
      {
         WorkArea wa = new WorkArea();
         boolean onClientSide = SessionManager.get().isLeaf();
         
         if (onClientSide)
         {
            ThinClient tc = ThinClient.getInstance();
            if (tc != null)
            {
               wa.cc = tc.getOutputManager().coordinates();
               wa.cm = ConfigManager.getInstance();
            }
         }
         else
         {
            wa.cc = LogicalTerminal.coordinates();
            wa.cm = ConfigManager.getInstance();
         }
         
         return wa;
      }
   };

   /** Thread local variable holding the number of suspensions. */
   private static ThreadLocal<Integer> suspendCount = new ThreadLocal<Integer>() 
   {
      @Override
      protected Integer initialValue()
      {
         return 0;
      }
   };
   
   /**
    * The common pointcut for all the advices. This covers server and
    * client UI calls, and excludes deserialization, cloning
    * operations and methods marked by {@link NoSyncCoordinates} annotation.
    */
   @Pointcut("within(com.goldencode.p2j.ui..*)" +
             "&& !withincode(com.goldencode.p2j.ui.*Config.new(..)) " +
             "&& !withincode(com.goldencode.p2j.ui.ButtonImageDefinition.new(..)) " +
             "&& !withincode(void readExternal(..)) " +
             "&& !withincode(void applyConfig(..)) ")
   public void withinCommonPoint()
   {
   }

   /**
    * Suspends coordinate synchronization for the current thread.
    * Every suspend call must be paired off with one call to {@link #resume()}.
    */
   public static void suspend()
   {
      int nextCount = suspendCount.get().intValue() + 1;
      suspendCount.set(nextCount);
   }
   
   /**
    * Resumes coordinate synchronization previously suspended with
    * {@link #resume()}.
    */
   public static void resume()
   {
      int nextCount = suspendCount.get().intValue() - 1;
      
      if (nextCount < 0)
      {
         throw new IllegalStateException("Suspend count out of balance!");
      }
      
      suspendCount.set(nextCount);
   }
   
   /**
    * Returns <code>true</code> when coordinate synchronization has been
    * suspended with {@link #suspend()}. 
    * 
    * @return   See above.
    */
   public static boolean isSuspended()
   {
      return suspendCount.get() > 0;
   }
   
   /**
    * This advice is executed for methods annotated with {@link NoSyncCoordinates}.
    * It suspends coordinate synchronization with {@link #suspend()} before
    * the annotated method is executed and then resumes with {@link #resume()}
    * after the annotated method returns.
    * 
    * @param    jp
    *           A reference to {@link ProceedingJoinPoint}.
    * 
    * @return   Returns the result of the executed method.   
    */
   @Around("execution(@com.goldencode.p2j.ui.NoSyncCoordinates * *(..))")
   public Object suspendAround(ProceedingJoinPoint jp)
   {
      try
      {
         suspend();
         return jp.proceed();
      }
      catch (Throwable e)
      {
         throw new RuntimeException(e);
      }
      finally
      {
         resume();
      }
   }
   
   /**
    * The after-advice for {@link BaseConfig#initColumn}.
    * 
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.BaseConfig.initColumn)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetInitColumn(BaseConfig cfg)
   {
      if (isSuspended())
         return;
      
      cfg.initColumn = Coordinate.scale(cfg.initColumn); 
      
      // handle the uninitialized case
      if (cfg.initColumn == BaseConfig.INV_COORD)
      {
         cfg.initX = BaseConfig.INV_COORD;
         return;
      }
      
      WorkArea wa = contextData.get();
      
      cfg.initX = wa.cc.columnToPixels(cfg.initColumn);
      cfg.initColumn = wa.cc.columnFromPixels(cfg.initX); 
   }
   
   /**
    * The after-advice for {@link BaseConfig#initRow}.
    * 
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.BaseConfig.initRow)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetInitRow(BaseConfig cfg)
   {
      if (isSuspended())
         return;
      
      cfg.initRow = Coordinate.scale(cfg.initRow);
            
      // handle the uninitialized case
      if (cfg.initRow == BaseConfig.INV_COORD)
      {
         cfg.initY = BaseConfig.INV_COORD;
         return;
      }
      
      WorkArea wa = contextData.get();
      
      cfg.initY = wa.cc.rowToPixels(cfg.initRow);
      cfg.initRow = wa.cc.rowFromPixels(cfg.initY); 
   }
   
   /**
    * The after-advice for {@link BaseConfig#initX}.
    * 
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.BaseConfig.initX)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetInitX(BaseConfig cfg)
   {
      if (isSuspended())
         return;
      
      // handle the uninitialized case
      if (cfg.initX == BaseConfig.INV_COORD)
      {
         cfg.initColumn = BaseConfig.INV_COORD;
         return;
      }
      
      WorkArea wa = contextData.get();
      
      cfg.initColumn = wa.cc.columnFromPixels(cfg.initX);
   }
   
   /**
    * The after-advice for {@link BaseConfig#initY}.
    * 
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.BaseConfig.initY)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetInitY(BaseConfig cfg)
   {
      if (isSuspended())
         return;
      
      // handle the uninitialized case
      if (cfg.initY == BaseConfig.INV_COORD)
      {
         cfg.initRow = BaseConfig.INV_COORD;
         return;
      }
      
      WorkArea wa = contextData.get();
      
      cfg.initRow = wa.cc.rowFromPixels(cfg.initY);
   }
   
   /**
    * The after-advice for {@link BaseConfig#column}.
    * 
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.BaseConfig.column)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetColumn(BaseConfig cfg)
   {
      if (isSuspended())
         return;
      
      cfg.column = Coordinate.scale(cfg.column); 
      
      // handle the uninitialized case
      if (cfg.column == BaseConfig.INV_COORD)
      {
         cfg.x = BaseConfig.INV_COORD;
         return;
      }
      
      WorkArea wa = contextData.get();
      
      cfg.x = wa.cc.columnToPixels(cfg.column);
      cfg.column = wa.cc.columnFromPixels(cfg.x); 
   }
   
   /**
    * The after-advice for {@link BaseConfig#row}.
    * 
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.BaseConfig.row)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetRow(BaseConfig cfg)
   {
      if (isSuspended())
         return;
      
      cfg.row = Coordinate.scale(cfg.row);
            
      // handle the uninitialized case
      if (cfg.row == BaseConfig.INV_COORD)
      {
         cfg.y = BaseConfig.INV_COORD;
         return;
      }
      
      WorkArea wa = contextData.get();
      
      cfg.y = wa.cc.rowToPixels(cfg.row);
      cfg.row = wa.cc.rowFromPixels(cfg.y); 
   }
   
   /**
    * The after-advice for {@link BaseConfig#x}.
    * 
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.BaseConfig.x)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetX(BaseConfig cfg)
   {
      if (isSuspended())
         return;
      
      // handle the uninitialized case
      if (cfg.x == BaseConfig.INV_COORD)
      {
         cfg.column = BaseConfig.INV_COORD;
         return;
      }
      
      WorkArea wa = contextData.get();
      
      cfg.column = wa.cc.columnFromPixels(cfg.x);
   }
   
   /**
    * The after-advice for {@link BaseConfig#y}.
    * 
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.BaseConfig.y)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetY(BaseConfig cfg)
   {
      if (isSuspended())
         return;
      
      // handle the uninitialized case
      if (cfg.y == BaseConfig.INV_COORD)
      {
         cfg.row = BaseConfig.INV_COORD;
         return;
      }
      
      WorkArea wa = contextData.get();
      
      cfg.row = wa.cc.rowFromPixels(cfg.y);
   }
   
   /**
    * The after-advice for {@link BaseConfig#clientColumn}.
    * 
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.BaseConfig.clientColumn)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetClientColumn(BaseConfig cfg)
   {
      if (isSuspended())
         return;
      
      cfg.clientColumn = Coordinate.scale(cfg.clientColumn); 
      
      // handle the uninitialized case
      if (cfg.clientColumn == BaseConfig.INV_COORD)
      {
         cfg.clientX = BaseConfig.INV_COORD;
         return;
      }
      
      WorkArea wa = contextData.get();
      
      cfg.clientX = wa.cc.columnToPixels(cfg.clientColumn);
      cfg.clientColumn = wa.cc.columnFromPixels(cfg.clientX); 
   }
   
   /**
    * The after-advice for {@link BaseConfig#clientRow}.
    * 
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.BaseConfig.clientRow)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetClientRow(BaseConfig cfg)
   {
      if (isSuspended())
         return;
      
      cfg.clientRow = Coordinate.scale(cfg.clientRow);
            
      // handle the uninitialized case
      if (cfg.clientRow == BaseConfig.INV_COORD)
      {
         cfg.clientY = BaseConfig.INV_COORD;
         return;
      }
      
      WorkArea wa = contextData.get();
      
      cfg.clientY = wa.cc.rowToPixels(cfg.clientRow);
      cfg.clientRow = wa.cc.rowFromPixels(cfg.clientY); 
   }
   
   /**
    * The after-advice for {@link BaseConfig#clientX}.
    * 
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.BaseConfig.clientX)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetClientX(BaseConfig cfg)
   {
      if (isSuspended())
         return;
      
      // handle the uninitialized case
      if (cfg.clientX == BaseConfig.INV_COORD)
      {
         cfg.clientColumn = BaseConfig.INV_COORD;
         return;
      }
      
      WorkArea wa = contextData.get();
      
      cfg.clientColumn = wa.cc.columnFromPixels(cfg.clientX);
   }
   
   /**
    * The after-advice for {@link BaseConfig#clientY}.
    * 
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.BaseConfig.clientY)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetClientY(BaseConfig cfg)
   {
      if (isSuspended())
         return;
      
      // handle the uninitialized case
      if (cfg.clientY == BaseConfig.INV_COORD)
      {
         cfg.clientRow = BaseConfig.INV_COORD;
         return;
      }
      
      WorkArea wa = contextData.get();
      
      cfg.clientRow = wa.cc.rowFromPixels(cfg.clientY);
   }
   
   /**
    * The after-advice for WIDTH-CHARS attribute.
    * 
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.BaseConfig.widthChars) " +
          "&& !withincode(private void com.goldencode.p2j.ui.LabelConfig.setDefaults(..)) " +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetWidthChars(BaseConfig cfg)
   {
      if (isSuspended())
         return;
      
      WorkArea wa = contextData.get();
      
      cfg.widthPixels = wa.cc.columnToPixels(cfg.widthChars);
      cfg.widthChars = wa.cc.columnFromPixels(cfg.widthPixels); 
   }
   
   /**
    * The after-advice for HEIGHT-CHARS attribute.
    * 
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.BaseConfig.heightChars)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetHeightChars(BaseConfig cfg)
   {
      if (isSuspended())
         return;
      
      WorkArea wa = contextData.get();
      
      cfg.heightPixels = wa.cc.rowToPixels(cfg.heightChars);
      cfg.heightChars = wa.cc.rowFromPixels(cfg.heightPixels);
   }

   /**
    * The after-advice for WIDTH-PIXELS attribute.
    * 
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.BaseConfig.widthPixels)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetWidthPixels(BaseConfig cfg)
   {
      if (isSuspended())
         return;
      
      WorkArea wa = contextData.get();
      
      cfg.widthChars = wa.cc.columnFromPixels(cfg.widthPixels);
   }

   /**
    * The after-advice for HEIGHT-PIXELS attribute.
    * 
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.BaseConfig.heightPixels)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetHeightPixels(BaseConfig cfg)
   {
      if (isSuspended())
         return;
      
      WorkArea wa = contextData.get();
      
      cfg.heightChars = wa.cc.rowFromPixels(cfg.heightPixels);
   }
   

   /**
    * The after-advice for WIDTH-CHARS attribute.
    * 
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.BaseConfig.clientWidthChars) " +
          "&& !withincode(private void com.goldencode.p2j.ui.LabelConfig.setDefaults(..)) " +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetClientWidthChars(BaseConfig cfg)
   {
      if (isSuspended())
         return;
      
      // handle the uninitialized case
      if (cfg.clientWidthChars == BaseConfig.INV_COORD)
      {
         cfg.clientWidthPixels = BaseConfig.INV_COORD;
         return;
      }
      
      WorkArea wa = contextData.get();
      
      cfg.clientWidthPixels = wa.cc.columnToPixels(cfg.clientWidthChars);
      cfg.clientWidthChars = wa.cc.columnFromPixels(cfg.clientWidthPixels); 
   }
   
   /**
    * The after-advice for HEIGHT-CHARS attribute.
    * 
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.BaseConfig.clientHeightChars)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetClientHeightChars(BaseConfig cfg)
   {
      if (isSuspended())
         return;
      
      // handle the uninitialized case
      if (cfg.clientHeightChars == BaseConfig.INV_COORD)
      {
         cfg.clientHeightPixels = BaseConfig.INV_COORD;
         return;
      }
      
      WorkArea wa = contextData.get();
      
      cfg.clientHeightPixels = wa.cc.rowToPixels(cfg.clientHeightChars);
      cfg.clientHeightChars = wa.cc.rowFromPixels(cfg.clientHeightPixels); 
   }

   /**
    * The after-advice for WIDTH-PIXELS attribute.
    * 
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.BaseConfig.clientWidthPixels)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetClientWidthPixels(BaseConfig cfg)
   {
      if (isSuspended())
         return;
      
      // handle the uninitialized case
      if (cfg.clientWidthPixels == BaseConfig.INV_COORD)
      {
         cfg.clientWidthChars = BaseConfig.INV_COORD;
         return;
      }
      
      WorkArea wa = contextData.get();
      
      cfg.clientWidthChars = wa.cc.columnFromPixels(cfg.clientWidthPixels);
   }

   /**
    * The after-advice for HEIGHT-PIXELS attribute.
    * 
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.BaseConfig.clientHeightPixels)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetClientHeightPixels(BaseConfig cfg)
   {
      if (isSuspended())
         return;
      
      // handle the uninitialized case
      if (cfg.clientHeightPixels == BaseConfig.INV_COORD)
      {
         cfg.clientHeightChars = BaseConfig.INV_COORD;
         return;
      }
      
      WorkArea wa = contextData.get();
      
      cfg.clientHeightChars = wa.cc.rowFromPixels(cfg.clientHeightPixels);
   }
   
   /**
    * The after-advice for MIN-WIDTH-CHARS attribute.
    * 
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.WindowConfig.minWidthChars)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetMinWidthChars(WindowConfig cfg)
   {
      if (isSuspended())
         return;
      
      WorkArea wa = contextData.get();
      
      cfg.minWidthPixels = wa.cc.columnToPixels(cfg.minWidthChars);
      cfg.minWidthChars = wa.cc.columnFromPixels(cfg.minWidthPixels); 
   }
   
   /**
    * The after-advice for MIN-HEIGHT-CHARS attribute.
    * 
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.WindowConfig.minHeightChars)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetMinHeightChars(WindowConfig cfg)
   {
      if (isSuspended())
         return;
      
      WorkArea wa = contextData.get();
      
      cfg.minHeightPixels = wa.cc.rowToPixels(cfg.minHeightChars);
      cfg.minHeightChars = wa.cc.rowFromPixels(cfg.minHeightPixels); 
   }

   /**
    * The after-advice for MIN-WIDTH-PIXELS attribute.
    * 
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.WindowConfig.minWidthPixels)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetMinWidthPixels(WindowConfig cfg)
   {
      if (isSuspended())
         return;
      
      WorkArea wa = contextData.get();
      
      cfg.minWidthChars = wa.cc.columnFromPixels(cfg.minWidthPixels);
   }

   /**
    * The after-advice for MIN-HEIGHT-PIXELS attribute.
    * 
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.WindowConfig.minHeightPixels)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetMinHeightPixels(WindowConfig cfg)
   {
      if (isSuspended())
         return;
      
      WorkArea wa = contextData.get();
      
      cfg.minHeightChars = wa.cc.rowFromPixels(cfg.minHeightPixels);
   }
   
   /**
    * The after-advice for MAX-WIDTH-CHARS attribute.
    * 
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.WindowConfig.maxWidthChars)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetMaxWidthChars(WindowConfig cfg)
   {
      if (isSuspended())
         return;
      
      WorkArea wa = contextData.get();
      
      cfg.maxWidthPixels = wa.cc.columnToPixels(cfg.maxWidthChars);
      cfg.maxWidthChars = wa.cc.columnFromPixels(cfg.maxWidthPixels); 
   }
   
   /**
    * The after-advice for MAX-HEIGHT-CHARS attribute.
    * 
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.WindowConfig.maxHeightChars)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetMaxHeightChars(WindowConfig cfg)
   {
      if (isSuspended())
         return;
      
      WorkArea wa = contextData.get();
      
      cfg.maxHeightPixels = wa.cc.rowToPixels(cfg.maxHeightChars);
      cfg.maxHeightChars = wa.cc.rowFromPixels(cfg.maxHeightPixels); 
   }

   /**
    * The after-advice for MAX-WIDTH-PIXELS attribute.
    * 
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.WindowConfig.maxWidthPixels)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetMaxWidthPixels(WindowConfig cfg)
   {
      if (isSuspended())
         return;
      
      WorkArea wa = contextData.get();
      
      cfg.maxWidthChars = wa.cc.columnFromPixels(cfg.maxWidthPixels);
   }

   /**
    * The after-advice for MAX-HEIGHT-PIXELS attribute.
    * 
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.WindowConfig.maxHeightPixels)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetMaxHeightPixels(WindowConfig cfg)
   {
      if (isSuspended())
         return;
      
      WorkArea wa = contextData.get();
      
      cfg.maxHeightChars = wa.cc.rowFromPixels(cfg.maxHeightPixels);
   }
   
   /**
    * The after-advice for FULL-WIDTH-CHARS attribute.
    * 
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.WindowConfig.fullWidthChars)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetFullWidthChars(WindowConfig cfg)
   {
      if (isSuspended())
         return;
      
      WorkArea wa = contextData.get();
      
      cfg.fullWidthPixels = wa.cc.columnToPixels(cfg.fullWidthChars);
      cfg.fullWidthChars = wa.cc.columnFromPixels(cfg.fullWidthPixels); 
   }
   
   /**
    * The after-advice for FULL-HEIGHT-CHARS attribute.
    * 
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.WindowConfig.fullHeightChars)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetFullHeightChars(WindowConfig cfg)
   {
      if (isSuspended())
         return;
      
      WorkArea wa = contextData.get();
      
      cfg.fullHeightPixels = wa.cc.rowToPixels(cfg.fullHeightChars);
      cfg.fullHeightChars = wa.cc.rowFromPixels(cfg.fullHeightPixels); 
   }

   /**
    * The after-advice for FULL-WIDTH-PIXELS attribute.
    * 
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.WindowConfig.fullWidthPixels)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetFullWidthPixels(WindowConfig cfg)
   {
      if (isSuspended())
         return;
      
      WorkArea wa = contextData.get();
      
      cfg.fullWidthChars = wa.cc.columnFromPixels(cfg.fullWidthPixels);
   }

   /**
    * The after-advice for FULL-HEIGHT-PIXELS attribute.
    * 
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.WindowConfig.fullHeightPixels)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetFullHeightPixels(WindowConfig cfg)
   {
      if (isSuspended())
         return;
      
      WorkArea wa = contextData.get();
      
      cfg.fullHeightChars = wa.cc.rowFromPixels(cfg.fullHeightPixels);
   }
   
   /**
    * The after-advice for VIRTUAL-WIDTH-CHARS attribute.
    * 
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.PaneConfig.virtualWidthChars)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetVirtWidthChars(PaneConfig cfg)
   {
      if (isSuspended())
         return;
      
      // handle the uninitialized case
      if (cfg.virtualWidthChars == BaseConfig.INV_COORD)
      {
         cfg.virtualWidthPixels = BaseConfig.INV_COORD;
         return;
      }

      WorkArea wa = contextData.get();
      
      cfg.virtualWidthPixels = wa.cc.columnToPixels(cfg.virtualWidthChars);
      cfg.virtualWidthChars = wa.cc.columnFromPixels(cfg.virtualWidthPixels); 
   }
   
   /**
    * The after-advice for VIRTUAL-HEIGHT-CHARS attribute.
    * 
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.PaneConfig.virtualHeightChars)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetVirtHeightChars(PaneConfig cfg)
   {
      if (isSuspended())
         return;
      
      // handle the uninitialized case
      if (cfg.virtualHeightChars == BaseConfig.INV_COORD)
      {
         cfg.virtualHeightPixels = BaseConfig.INV_COORD;
         return;
      }
      
      WorkArea wa = contextData.get();
      
      cfg.virtualHeightPixels = wa.cc.rowToPixels(cfg.virtualHeightChars);
      cfg.virtualHeightChars = wa.cc.rowFromPixels(cfg.virtualHeightPixels); 
   }

   /**
    * The after-advice for VIRTUAL-WIDTH-PIXELS attribute.
    * 
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.PaneConfig.virtualWidthPixels)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetVirtWidthPixels(PaneConfig cfg)
   {
      if (isSuspended())
         return;
      
      WorkArea wa = contextData.get();
      
      // handle the uninitialized case
      if (cfg.virtualWidthPixels == BaseConfig.INV_COORD)
      {
         cfg.virtualWidthChars = BaseConfig.INV_COORD;
         return;
      }
      
      cfg.virtualWidthChars = wa.cc.columnFromPixels(cfg.virtualWidthPixels);
   }

   /**
    * The after-advice for VIRTUAL-HEIGHT-PIXELS attribute.
    * 
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.PaneConfig.virtualHeightPixels)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetVirtHeightPixels(PaneConfig cfg)
   {
      if (isSuspended())
         return;
      
      // handle the uninitialized case
      if (cfg.virtualHeightPixels == BaseConfig.INV_COORD)
      {
         cfg.virtualHeightChars = BaseConfig.INV_COORD;
         return;
      }
      
      WorkArea wa = contextData.get();
      
      Coordinate.RoundingMode rmode = cfg.virtualHeightPixels < 0 ? 
                                      Coordinate.RoundingMode.TRUNCATE : 
                                      Coordinate.RoundingMode.ROUND;
      
      cfg.virtualHeightChars = wa.cc.rowFromPixels(cfg.virtualHeightPixels, rmode);
   }

   /**
    * The after-advice for {@link ButtonImageDefinition#heightChars}.
    *
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.ButtonImageDefinition.heightChars)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetButtonImageHeightChars(ButtonImageDefinition cfg)
   {
      if (isSuspended())
         return;

      // handle the uninitialized case
      if (cfg.heightChars == BaseConfig.INV_COORD)
      {
         cfg.heightPixels = BaseConfig.INV_COORD;
         return;
      }

      WorkArea wa = contextData.get();

      cfg.heightPixels = wa.cc.rowToPixels(cfg.heightChars);
      cfg.heightChars = wa.cc.rowFromPixels(cfg.heightPixels);
   }

   /**
    * The after-advice for {@link ButtonImageDefinition#widthChars}.
    *
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.ButtonImageDefinition.widthChars)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetButtonImageWidthChars(ButtonImageDefinition cfg)
   {
      if (isSuspended())
         return;

      // handle the uninitialized case
      if (cfg.widthChars == BaseConfig.INV_COORD)
      {
         cfg.widthPixels = BaseConfig.INV_COORD;
         return;
      }

      WorkArea wa = contextData.get();

      cfg.widthPixels = wa.cc.columnToPixels(cfg.widthChars);
      cfg.widthChars = wa.cc.columnFromPixels(cfg.widthPixels);
   }

   /**
    * The after-advice for {@link ButtonImageDefinition#heightPixels}.
    *
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.ButtonImageDefinition.heightPixels)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetButtonImageHeightPixels(ButtonImageDefinition cfg)
   {
      if (isSuspended())
         return;

      // handle the uninitialized case
      if (cfg.heightPixels == BaseConfig.INV_COORD)
      {
         cfg.heightChars = BaseConfig.INV_COORD;
         return;
      }

      WorkArea wa = contextData.get();

      cfg.heightChars = wa.cc.rowFromPixels(cfg.heightPixels);
   }

   /**
    * The after-advice for {@link ButtonImageDefinition#widthPixels}.
    *
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.ButtonImageDefinition.widthPixels)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetButtonImageWidthPixels(ButtonImageDefinition cfg)
   {
      if (isSuspended())
         return;

      // handle the uninitialized case
      if (cfg.widthPixels == BaseConfig.INV_COORD)
      {
         cfg.widthChars = BaseConfig.INV_COORD;
         return;
      }

      WorkArea wa = contextData.get();

      cfg.widthChars = wa.cc.columnFromPixels(cfg.widthPixels);
   }

   /**
    * The after-advice for {@link ButtonImageDefinition#offsetRow}.
    *
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.ButtonImageDefinition.offsetRow)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetButtonImageOffsetRow(ButtonImageDefinition cfg)
   {
      if (isSuspended())
         return;

      // handle the uninitialized case
      if (cfg.offsetRow == BaseConfig.INV_COORD)
      {
         cfg.offsetY = BaseConfig.INV_COORD;
         return;
      }

      WorkArea wa = contextData.get();

      cfg.offsetY = wa.cc.rowToPixels(cfg.offsetRow);
      cfg.offsetRow = wa.cc.rowFromPixels(cfg.offsetY);
   }

   /**
    * The after-advice for {@link ButtonImageDefinition#offsetCol}.
    *
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.ButtonImageDefinition.offsetCol)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetButtonImageOffsetCol(ButtonImageDefinition cfg)
   {
      if (isSuspended())
         return;

      // handle the uninitialized case
      if (cfg.offsetCol == BaseConfig.INV_COORD)
      {
         cfg.offsetX = BaseConfig.INV_COORD;
         return;
      }

      WorkArea wa = contextData.get();

      cfg.offsetX = wa.cc.columnToPixels(cfg.offsetCol);
      cfg.offsetCol = wa.cc.columnFromPixels(cfg.offsetX);
   }

   /**
    * The after-advice for {@link ButtonImageDefinition#offsetY}.
    *
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.ButtonImageDefinition.offsetY)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetButtonImageOffsetY(ButtonImageDefinition cfg)
   {
      if (isSuspended())
         return;

      // handle the uninitialized case
      if (cfg.offsetY == BaseConfig.INV_COORD)
      {
         cfg.offsetRow = BaseConfig.INV_COORD;
         return;
      }

      WorkArea wa = contextData.get();

      cfg.offsetRow = wa.cc.rowFromPixels(cfg.offsetY);
   }

   /**
    * The after-advice for {@link ButtonImageDefinition#offsetX}.
    *
    * @param   cfg
    *          The target config reference being intercepted.
    */
   @After("set(public * com.goldencode.p2j.ui.ButtonImageDefinition.offsetX)" +
          "&& target(cfg) " +
          "&& withinCommonPoint()")
   public void afterSetButtonImageOffsetX(ButtonImageDefinition cfg)
   {
      if (isSuspended())
         return;

      // handle the uninitialized case
      if (cfg.offsetX == BaseConfig.INV_COORD)
      {
         cfg.offsetCol = BaseConfig.INV_COORD;
         return;
      }

      WorkArea wa = contextData.get();

      cfg.offsetCol = wa.cc.columnFromPixels(cfg.offsetX);
   }

   /**
    * Class for cached values saved in the context. 
    **/
   private class WorkArea
   {
      /**
       * Cached coordinates conversion reference.
       */
      private CoordinatesConversion cc;
      
      /**
       * Cached config manager reference.
       */
      private ConfigManager cm;
   }
}