# 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;
       }
    }