homescreenapp/hsutils/src/hswidgetpositioningonwidgetadd.cpp
changeset 90 3ac3aaebaee5
parent 86 e4f038c420f7
child 94 6364b99b9e27
child 95 32e56106abf2
equal deleted inserted replaced
86:e4f038c420f7 90:3ac3aaebaee5
     1 /*
       
     2 * Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
       
     3 * All rights reserved.
       
     4 * This component and the accompanying materials are made available
       
     5 * under the terms of "Eclipse Public License v1.0"
       
     6 * which accompanies this distribution, and is available
       
     7 * at the URL "http://www.eclipse.org/legal/epl-v10.html".
       
     8 *
       
     9 * Initial Contributors:
       
    10 * Nokia Corporation - initial contribution.
       
    11 *
       
    12 * Contributors:
       
    13 *
       
    14 * Description: 
       
    15 *
       
    16 */
       
    17 
       
    18 #include <QLineF>
       
    19 #include <QtGlobal>
       
    20 #include <QPointF>
       
    21 #include <math.h>
       
    22 
       
    23 #include <HbInstance>
       
    24 
       
    25 
       
    26 #include "hswidgetpositioningonwidgetadd.h"
       
    27 
       
    28 const qreal offset = 40; //TODO: Implement this as configurable parameter
       
    29 
       
    30 
       
    31 /*!
       
    32     \class HsWidgetPositioningOnWidgetAdd
       
    33     \ingroup group_hsutils
       
    34     \brief 
       
    35 */
       
    36 
       
    37 /*!
       
    38     \class HsWidgetPositioningOnWidgetAdd
       
    39     \brief Defines widget positioning on widget add.
       
    40 
       
    41     Widget positioning on widget add sets positions for
       
    42     a set of home screen widgets added from application library.
       
    43 */
       
    44 
       
    45 /*!
       
    46     Sets the positioning \a instance as the current one.
       
    47     Deletes the existing instance if present.
       
    48 */
       
    49 void HsWidgetPositioningOnWidgetAdd::setInstance(
       
    50     HsWidgetPositioningOnWidgetAdd *instance)
       
    51 {
       
    52     if (mInstance)
       
    53         delete mInstance;
       
    54     mInstance = instance;
       
    55 }
       
    56  
       
    57 /*!
       
    58     Returns the current positioning instance.
       
    59 */
       
    60 HsWidgetPositioningOnWidgetAdd *HsWidgetPositioningOnWidgetAdd::instance()
       
    61 {
       
    62     return mInstance;
       
    63 }
       
    64 
       
    65 /*!
       
    66     Stores the current positioning instance.
       
    67 */
       
    68 HsWidgetPositioningOnWidgetAdd *HsWidgetPositioningOnWidgetAdd::mInstance = 0;
       
    69 
       
    70 /*!
       
    71     \class HsAnchorPointInBottomRight
       
    72     \brief Diagonal widget positioning algorithm.
       
    73     
       
    74     Sets widget's lower right corner to follow content area's diagonal.
       
    75     Widgets are positioned to certain offset to each other.
       
    76 */
       
    77 QList<QRectF> HsAnchorPointInBottomRight::convert(
       
    78     const QRectF &contentArea,
       
    79     const QList<QRectF> &existingRects,
       
    80     const QList<QRectF> &newRects,
       
    81     const QPointF &startPoint)
       
    82 {
       
    83     Q_UNUSED(existingRects);
       
    84 
       
    85     QList<QRectF> toGeometries;
       
    86 
       
    87     //Offset for widgets' bottom right position to each other
       
    88     qreal k = contentArea.height()/contentArea.width(); //slope of the diagonal
       
    89     qreal offset_x = offset/(sqrt(k + 1));
       
    90     qreal offset_y = k*offset_x;
       
    91     QPointF offsetPoint(offset_x, offset_y);
       
    92     
       
    93     QPointF anchorPoint;
       
    94    
       
    95     if(startPoint.isNull()){
       
    96 
       
    97         QLineF diagonal(contentArea.topLeft(), contentArea.bottomRight());
       
    98         QLineF widgetRightSide(contentArea.center().x()+ newRects.at(0).width()/2,
       
    99                            contentArea.top(),
       
   100                            contentArea.center().x()+ newRects.at(0).width()/2,
       
   101                            contentArea.bottom());
       
   102 
       
   103         // right side line intersection with diagonal will be bottom right position
       
   104         // for the first rect
       
   105         if(QLineF::BoundedIntersection != 
       
   106             diagonal.intersect(widgetRightSide, &anchorPoint)) {
       
   107             return newRects; //Return original since undefined error.
       
   108                             //In this case widget's must be wider than the content area.
       
   109         }
       
   110     }else{
       
   111         anchorPoint = startPoint - offsetPoint;
       
   112     }
       
   113 
       
   114     QRectF widgetRect;
       
   115     for(int i=0;i<newRects.count();++i) {
       
   116         widgetRect = newRects.at(i);
       
   117         widgetRect.moveBottomRight(anchorPoint);
       
   118         //if widget rect doesn't fit, try to move it
       
   119         if(!contentArea.contains(widgetRect)) {
       
   120             /*! precondition is that
       
   121              widget's max height < content area height
       
   122              widget's max widht < content area width
       
   123             */
       
   124             widgetRect.moveBottomRight(contentArea.bottomRight());
       
   125             // anchorPoin is always previous bottom right
       
   126             anchorPoint = widgetRect.bottomRight();
       
   127         }
       
   128         toGeometries << widgetRect;
       
   129         anchorPoint -= offsetPoint;
       
   130         
       
   131     }
       
   132     return toGeometries;
       
   133 }
       
   134 
       
   135 
       
   136 /*!
       
   137     \class HsAnchorPointInCenter
       
   138     \brief Diagonal widget positioning algorithm.
       
   139     
       
   140     Sets widget's center point to follow content area's diagonal.
       
   141     Widgets are positioned to certain offset to each other.
       
   142 */
       
   143 #ifdef COVERAGE_MEASUREMENT
       
   144 #pragma CTC SKIP
       
   145 #endif //COVERAGE_MEASUREMENT
       
   146 QList<QRectF> HsAnchorPointInCenter::convert(
       
   147     const QRectF &contentArea,
       
   148     const QList<QRectF> &existingRects,
       
   149     const QList<QRectF> &newRects,
       
   150     const QPointF &startPoint )
       
   151 {
       
   152     Q_UNUSED(existingRects);
       
   153     Q_UNUSED(startPoint)
       
   154 
       
   155     QList<QRectF> toGeometries;
       
   156 
       
   157     //Offset for widgets' centers position to each other
       
   158     qreal k = contentArea.height()/contentArea.width(); //slope of the diagonal
       
   159     qreal offset_x = offset/(sqrt(k + 1));
       
   160     qreal offset_y = k*offset_x;
       
   161     QPointF offsetPoint(offset_x, offset_y);
       
   162 
       
   163     //First widget to the center of the content area
       
   164     QPointF anchorPoint = contentArea.center();
       
   165     foreach (QRectF g, newRects) {
       
   166         g.moveCenter(anchorPoint);
       
   167         toGeometries << g;
       
   168         anchorPoint -= offsetPoint;
       
   169         if(!contentArea.contains(anchorPoint)) {
       
   170             anchorPoint = contentArea.bottomRight();
       
   171         }
       
   172     }
       
   173     return toGeometries;
       
   174 }
       
   175 
       
   176 /*!
       
   177     \class HsWidgetOrganizer
       
   178     \brief Advanced widget positioning algorithm.
       
   179     
       
   180     Organizes widget's starting from upper left corner towards right,
       
   181     and then continues the on the next line.
       
   182 */
       
   183 QList<QRectF> HsWidgetOrganizer::convert(
       
   184     const QRectF &contentArea,
       
   185     const QList<QRectF> &existingRects,
       
   186     const QList<QRectF> &newRects,
       
   187     const QPointF &startPoint)
       
   188 {
       
   189     Q_UNUSED(startPoint)
       
   190 
       
   191     // TODO: maybe we can utilize start point in some use cases / optimizations?
       
   192 
       
   193     QList<QRectF> toGeometries;
       
   194 
       
   195     // TODO: anchor distance to configuration?
       
   196     // TODO: optimize anchor distance based on new content amount
       
   197     // TODO: snap value to same as anchor distance?
       
   198     mAnchorDistance = 5;
       
   199     QList<bool> temp;
       
   200     mAnchors = temp;
       
   201 
       
   202     // test flag
       
   203 //    int test = 0;
       
   204 
       
   205     // initialize anchor network for widget positions
       
   206 //    if (test == 0) {
       
   207         initAnchors(contentArea.size());
       
   208 //    } else {
       
   209 //        mAnchorDistance = 2;
       
   210 //        initAnchors(QSizeF(6,6));
       
   211 //    }
       
   212 
       
   213     // mark existing rects (widgets) reserved
       
   214     foreach (QRectF rect, existingRects) {
       
   215         // TODO: could mStartWidthAnchorPoint, mEndWidthAnchorPoint, mEndHeightAnchorPoint be somehow refactored better way?
       
   216         mStartWidthAnchorPoint.setX(lenghtInAnchorPoints(rect.x() - contentArea.x()));
       
   217         mEndWidthAnchorPoint.setX(lenghtInAnchorPoints(rect.x() + rect.width() - contentArea.x()));
       
   218         mStartWidthAnchorPoint.setY(lenghtInAnchorPoints(rect.y() - contentArea.y()));
       
   219         mEndHeightAnchorPoint.setY(lenghtInAnchorPoints(rect.y() + rect.height() - contentArea.y()));
       
   220         // mark reserved anchor points
       
   221         markReservedAnchors();
       
   222         mStartWidthAnchorPoint = QPointF(0,0);
       
   223         mEndWidthAnchorPoint = QPointF(0,0);
       
   224         mEndHeightAnchorPoint = QPointF(0,0);
       
   225     }
       
   226 
       
   227     QList<QRectF> notOrganizedRects;
       
   228 
       
   229     // get positions for all new rects (widgets)
       
   230     for ( int i = 0; i < newRects.count(); i++) {
       
   231         bool found = false;
       
   232 //        if (test == 0) {
       
   233             // find first free anchor point for rect
       
   234             found = getAnchorPoint(newRects.at(i).size());
       
   235 //        } else {
       
   236 //            found = getAnchorPoint(QSizeF(2,2));
       
   237 //        }
       
   238 
       
   239         if (found) {
       
   240             // save to geometry list
       
   241             toGeometries << QRectF(mStartWidthAnchorPoint.x() * mAnchorDistance + contentArea.x(),
       
   242                                    mStartWidthAnchorPoint.y() * mAnchorDistance + contentArea.y(),
       
   243                                    newRects.at(i).width(), newRects.at(i).height());
       
   244             // mark new widgets rect reserved
       
   245             markReservedAnchors();
       
   246             // TODO: these optimizations could be used for empty page
       
   247             //mStartWidthAnchorPoint.setX(mEndWidthAnchorPoint.x() + 1);
       
   248             //mStartWidthAnchorPoint.setY(mEndWidthAnchorPoint.y());
       
   249         } else {
       
   250             // collect widgets that do not fit
       
   251             notOrganizedRects << newRects.at(i);
       
   252         }
       
   253         // TODO: remove these to optimize for empty page
       
   254         mStartWidthAnchorPoint = QPointF(0,0);
       
   255         mEndWidthAnchorPoint = QPointF(0,0);
       
   256     }
       
   257 
       
   258     // use center algorithm with offset for the rest widget that did not fit to screen
       
   259     if (notOrganizedRects.count() > 0) {
       
   260         QList<QRectF> tmpExistingRects;
       
   261         tmpExistingRects += newRects;
       
   262         tmpExistingRects += existingRects;
       
   263         HsAnchorPointInCenter *centerAlgorithm = new HsAnchorPointInCenter();
       
   264         QList<QRectF> calculatedRects =
       
   265             centerAlgorithm->convert(contentArea, tmpExistingRects, notOrganizedRects, QPointF());
       
   266         toGeometries += calculatedRects;
       
   267         delete centerAlgorithm;
       
   268     }
       
   269 
       
   270     return toGeometries;
       
   271 }
       
   272 
       
   273 
       
   274 /*!    
       
   275     Initializes anchor points for context area
       
   276 */
       
   277 bool HsWidgetOrganizer::initAnchors(const QSizeF &areaSize)
       
   278 {
       
   279     // mandatory check ups
       
   280     // TODO: these mAnchorDistance checks to earlier phase
       
   281     if (areaSize == QSizeF(0,0) || areaSize.width() < mAnchorDistance ||
       
   282         areaSize.height() < mAnchorDistance || mAnchorDistance == 0 || mAnchorDistance == 1) {
       
   283         return false;
       
   284     }
       
   285     mAnchorColumns = 0;
       
   286     mAnchorRows = 0;
       
   287 
       
   288     // TODO: can we optimize anchor amount utilizing minimum widget size
       
   289     mAnchorColumns = lenghtInAnchorPoints(areaSize.width());
       
   290     mAnchorRows = lenghtInAnchorPoints(areaSize.height());
       
   291 
       
   292     // create anchor network
       
   293     for (int i = 0; i < (mAnchorRows * mAnchorColumns); i++) {
       
   294         mAnchors << false;
       
   295     }
       
   296     // zero start points
       
   297     mStartWidthAnchorPoint = QPointF(0,0);
       
   298     mEndWidthAnchorPoint = QPointF(0,0);
       
   299 
       
   300     return true;
       
   301 }
       
   302 
       
   303 /*!    
       
   304     Finds anchor points for content size
       
   305 */
       
   306 bool HsWidgetOrganizer::getAnchorPoint(const QSizeF &contentSize)
       
   307 {
       
   308     bool anchorFound = false;
       
   309 
       
   310     while (anchorFound == false) {
       
   311         // if no width found for content
       
   312         if (!searchWidthSpace(contentSize)) {
       
   313             // when content organized in height order remove this line for optimization
       
   314             mStartWidthAnchorPoint = QPointF(0,0);
       
   315             mEndWidthAnchorPoint = QPointF(0,0);
       
   316             return false;
       
   317         }
       
   318         // search height for content
       
   319         int height = lenghtInAnchorPoints(contentSize.height());
       
   320         anchorFound = searchHeightSpace(height);
       
   321     }
       
   322     return true;
       
   323 }
       
   324 
       
   325 /*!    
       
   326     Searches anchor point width for content size
       
   327 */
       
   328 bool HsWidgetOrganizer::searchWidthSpace(const QSizeF &contentSize) 
       
   329 {
       
   330     int availableWidth = 0;    
       
   331     int contentWidth = lenghtInAnchorPoints(contentSize.width());
       
   332     // TODO: use this optimizations for empty page
       
   333     //int contentHeight = lenghtInAnchorPoints(contentSize.height());
       
   334     bool newRow = true;
       
   335 
       
   336     for (int i = getIndexForCoordinate(mStartWidthAnchorPoint); i <= mAnchors.count(); i++) {
       
   337         // no width left on the page
       
   338         if ((newRow == false) && ((i % (mAnchorColumns)) == 0)) {
       
   339             availableWidth = 0;
       
   340             // jump to new row
       
   341             mStartWidthAnchorPoint.setX(0);
       
   342             // TODO: use this optimizations for empty page
       
   343             //mStartWidthAnchorPoint.setY(mStartWidthAnchorPoint.y() + contentHeight + 1);
       
   344             mStartWidthAnchorPoint.setY(mStartWidthAnchorPoint.y() + 1);
       
   345             i = getIndexForCoordinate(mStartWidthAnchorPoint) - 1;
       
   346             // if no height found
       
   347             if (i < 0) {
       
   348                 return false;
       
   349             }
       
   350             newRow = true;
       
   351         } else {
       
   352             // if enough width found
       
   353             if (availableWidth == contentWidth) {
       
   354                 mEndWidthAnchorPoint = getAnchorCoordinates(i);
       
   355                 if (mEndWidthAnchorPoint == QPointF()) {
       
   356                     return false;
       
   357                 }
       
   358                 return true;
       
   359             }
       
   360             // if anchor reserved
       
   361             if (mAnchors[i] == true) {
       
   362                 availableWidth = 0;
       
   363             } else {
       
   364                 // update available width
       
   365                 availableWidth = availableWidth + 1;
       
   366             }
       
   367             newRow = false;
       
   368         }   
       
   369     }
       
   370     return false;
       
   371 }
       
   372 
       
   373 /*!    
       
   374     Searches anchor point area for content size
       
   375 */
       
   376 bool HsWidgetOrganizer::searchHeightSpace(int contentHeight)
       
   377 {
       
   378     mEndHeightAnchorPoint = QPointF(0,0);
       
   379  
       
   380     for (int i = mStartWidthAnchorPoint.x(); i <= mEndWidthAnchorPoint.x(); i++) {
       
   381         for (int j = mStartWidthAnchorPoint.y(); j <= (mStartWidthAnchorPoint.y() + contentHeight); j++) {
       
   382             int index = getIndexForCoordinate(QPointF(i,j));
       
   383             // check that index is not out of bounds
       
   384             if (index == -1) {
       
   385                 // update start width point one step
       
   386                 mStartWidthAnchorPoint.setX(mStartWidthAnchorPoint.x() + 1); 
       
   387                 return false;
       
   388             }
       
   389             // if anchor reserved
       
   390             if (mAnchors[index] == true) {
       
   391                 // update start width point one step
       
   392                 mStartWidthAnchorPoint.setX(mStartWidthAnchorPoint.x() + 1);
       
   393                 return false;
       
   394             }
       
   395         }
       
   396     }
       
   397     mEndHeightAnchorPoint = QPointF(mEndWidthAnchorPoint.x(), mEndWidthAnchorPoint.y() + contentHeight);
       
   398     return true;
       
   399 }
       
   400 
       
   401 /*!    
       
   402     Marks reserved anchor points based on pre-defined starting and ending points
       
   403 */
       
   404 bool HsWidgetOrganizer::markReservedAnchors()
       
   405 {
       
   406     for (int i = mStartWidthAnchorPoint.x(); i <= mEndWidthAnchorPoint.x(); i++) {
       
   407         for (int j = mStartWidthAnchorPoint.y(); j <= mEndHeightAnchorPoint.y(); j++) {
       
   408             mAnchors[getIndexForCoordinate(QPointF(i,j))] = true;
       
   409         }
       
   410     }
       
   411     return true;
       
   412 }
       
   413 
       
   414 /*!    
       
   415     Returns pixel coordinate based on anchor coordinate
       
   416 */
       
   417 QPointF HsWidgetOrganizer::getAnchorCoordinates(int index)
       
   418 {
       
   419     if (index < mAnchors.count()) {
       
   420         int x = index % mAnchorColumns;
       
   421         int y = (index - x) / mAnchorColumns;
       
   422         return QPointF(x,y);
       
   423     } else {
       
   424         return QPointF();
       
   425     }
       
   426 }
       
   427 
       
   428 /*!    
       
   429     Returns anchor coordinate based on pixel coordinate
       
   430 */
       
   431 int HsWidgetOrganizer::getIndexForCoordinate(QPointF position)
       
   432 {
       
   433     int index = (position.y() * mAnchorColumns) + position.x();
       
   434     if (index < mAnchors.count()) {
       
   435         return index;
       
   436     } else {
       
   437         return -1;
       
   438     }
       
   439 }
       
   440 
       
   441 /*!    
       
   442     Calculates pixel length as anchor points
       
   443 */
       
   444 int HsWidgetOrganizer::lenghtInAnchorPoints(QVariant length)
       
   445 {
       
   446     // check remainder
       
   447     int remainder = length.toInt() % mAnchorDistance;
       
   448     return ((length.toInt() - remainder) / mAnchorDistance);
       
   449 }
       
   450 
       
   451 #ifdef COVERAGE_MEASUREMENT
       
   452 #pragma CTC ENDSKIP
       
   453 #endif //COVERAGE_MEASUREMENT
       
   454