homescreenapp/hsdomainmodel/src/hswidgetpositioningonwidgetadd.cpp
changeset 90 3ac3aaebaee5
child 95 32e56106abf2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/homescreenapp/hsdomainmodel/src/hswidgetpositioningonwidgetadd.cpp	Mon Sep 20 10:19:07 2010 +0300
@@ -0,0 +1,627 @@
+/*
+* Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+* All rights reserved.
+* This component and the accompanying materials are made available
+* under the terms of "Eclipse Public License v1.0"
+* which accompanies this distribution, and is available
+* at the URL "http://www.eclipse.org/legal/epl-v10.html".
+*
+* Initial Contributors:
+* Nokia Corporation - initial contribution.
+*
+* Contributors:
+*
+* Description: 
+*
+*/
+
+#include <QLineF>
+#include <QtGlobal>
+#include <QPointF>
+#include <math.h>
+
+#include "hswidgetpositioningonwidgetadd.h"
+#include "hsconfiguration.h"
+#include "hsgui.h"
+
+const qreal offset = 20; //TODO: Implement this as configurable parameter
+
+
+/*!
+    \class HsWidgetPositioningOnWidgetAdd
+    \ingroup group_hsutils
+    \brief 
+*/
+
+/*!
+    \class HsWidgetPositioningOnWidgetAdd
+    \brief Defines widget positioning on widget add.
+
+    Widget positioning on widget add sets positions for
+    a set of home screen widgets added from application library.
+*/
+
+/*!
+    Sets the positioning \a instance as the current one.
+    Deletes the existing instance if present.
+*/
+void HsWidgetPositioningOnWidgetAdd::setInstance(
+    HsWidgetPositioningOnWidgetAdd *instance)
+{
+    if (mInstance)
+        delete mInstance;
+    mInstance = instance;
+}
+ 
+/*!
+    Returns the current positioning instance.
+*/
+HsWidgetPositioningOnWidgetAdd *HsWidgetPositioningOnWidgetAdd::instance()
+{
+    return mInstance;
+}
+
+/*!
+    Stores the current positioning instance.
+*/
+HsWidgetPositioningOnWidgetAdd *HsWidgetPositioningOnWidgetAdd::mInstance = 0;
+
+/*!
+    \class HsAnchorPointInBottomRight
+    \brief Diagonal widget positioning algorithm.
+    
+    Sets widget's lower right corner to follow content area's diagonal.
+    Widgets are positioned to certain offset to each other.
+*/
+QList<QRectF> HsAnchorPointInBottomRight::convert(
+    const QRectF &contentArea,
+    const QList<QRectF> &existingRects,
+    const QList<QRectF> &newRects,
+    const QPointF &startPoint)
+{
+    Q_UNUSED(existingRects);
+
+    QList<QRectF> toGeometries;
+
+    //Offset for widgets' bottom right position to each other
+    qreal k = contentArea.height()/contentArea.width(); //slope of the diagonal
+    qreal offset_x = offset/(sqrt(k + 1));
+    qreal offset_y = k*offset_x;
+    QPointF offsetPoint(offset_x, offset_y);
+    
+    QPointF anchorPoint;
+   
+    if(startPoint.isNull()){
+
+        QLineF diagonal(contentArea.topLeft(), contentArea.bottomRight());
+        QLineF widgetRightSide(contentArea.center().x()+ newRects.at(0).width()/2,
+                           contentArea.top(),
+                           contentArea.center().x()+ newRects.at(0).width()/2,
+                           contentArea.bottom());
+
+        // right side line intersection with diagonal will be bottom right position
+        // for the first rect
+        if(QLineF::BoundedIntersection != 
+            diagonal.intersect(widgetRightSide, &anchorPoint)) {
+            return newRects; //Return original since undefined error.
+                            //In this case widget's must be wider than the content area.
+        }
+    }else{
+        anchorPoint = startPoint - offsetPoint;
+    }
+
+    QRectF widgetRect;
+    for(int i=0;i<newRects.count();++i) {
+        widgetRect = newRects.at(i);
+        widgetRect.moveBottomRight(anchorPoint);
+        //if widget rect doesn't fit, try to move it
+        if(!contentArea.contains(widgetRect)) {
+            /*! precondition is that
+             widget's max height < content area height
+             widget's max widht < content area width
+            */
+            widgetRect.moveBottomRight(contentArea.bottomRight());
+            // anchorPoin is always previous bottom right
+            anchorPoint = widgetRect.bottomRight();
+        }
+        toGeometries << widgetRect;
+        anchorPoint -= offsetPoint;
+        
+    }
+    return toGeometries;
+}
+
+/*!
+    \class HsAnchorPointInCenter
+    \brief Diagonal widget positioning algorithm.
+    
+    Sets widget's center point to follow content area's diagonal.
+    Widgets are positioned to certain offset to each other.
+*/
+#ifdef COVERAGE_MEASUREMENT
+#pragma CTC SKIP
+#endif //COVERAGE_MEASUREMENT
+QList<QRectF> HsAnchorPointInCenter::convert(
+    const QRectF &contentArea,
+    const QList<QRectF> &existingRects,
+    const QList<QRectF> &newRects,
+    const QPointF &startPoint )
+{
+    Q_UNUSED(existingRects);
+    Q_UNUSED(startPoint)
+
+    QList<QRectF> toGeometries;
+
+    //Offset for widgets' centers position to each other
+    qreal k = contentArea.height()/contentArea.width(); //slope of the diagonal
+    qreal offset_x = offset/(sqrt(k + 1));
+    qreal offset_y = k*offset_x;
+    QPointF offsetPoint(offset_x, offset_y);
+
+    //First widget to the center of the content area
+    QPointF anchorPoint = contentArea.center();
+    foreach (QRectF g, newRects) {
+        g.moveCenter(anchorPoint);
+        toGeometries << g;
+        anchorPoint -= offsetPoint;
+        if(!contentArea.contains(anchorPoint)) {
+            anchorPoint = contentArea.bottomRight();
+        }
+    }
+    return toGeometries;
+}
+
+HsWidgetOrganizer::HsWidgetOrganizer(int anchorDistance,
+                                     HsConfiguration::WidgetOrganizerSearchSequence sequence)
+: mAnchorDistance(anchorDistance), mSequence(sequence), mAnchorColumns(0), mAnchorRows(0),
+  mCenterAlgorithm(new HsAnchorPointInCenter())
+{
+
+}
+
+HsWidgetOrganizer::~HsWidgetOrganizer()
+{
+    delete mCenterAlgorithm;
+}
+
+/*!
+    \class HsWidgetOrganizer
+    \brief Advanced widget positioning algorithm.
+    
+    Organizes widget's starting from upper left corner towards right,
+    and then continues the on the next line.
+*/
+QList<QRectF> HsWidgetOrganizer::convert(
+    const QRectF &contentArea,
+    const QList<QRectF> &existingRects,
+    const QList<QRectF> &newRects,
+    const QPointF &startPoint)
+{
+    Q_UNUSED(startPoint)
+
+    // mandatory check ups
+    if (mAnchorDistance <= 0 || contentArea == QRectF() ||
+        newRects == QList<QRectF>()) {
+        return QList<QRectF>();
+    }
+
+    // calculate anchor limits based on anchor distance
+    mAnchorColumns = convertToAnchors(contentArea.width());
+    mAnchorRows = convertToAnchors(contentArea.height());
+    mContentArea = contentArea;
+
+    // map rects so that we can later return them in original order
+    QMap<int, QRectF> newRectsMap;
+    for (int id = 0; id < newRects.count(); id++) {
+        newRectsMap.insert(id, newRects.at(id));
+    } 
+
+    // get orientation
+    Qt::Orientation orientation(HsGui::instance()->orientation());
+
+    SortMode mode;
+    // select sorting mode based on orientation and search sequence
+    if((orientation == Qt::Vertical && mSequence == HsConfiguration::SearchRowByRow) ||
+        (orientation == Qt::Horizontal && mSequence == HsConfiguration::SearchColumnByColumn)) {
+        mode = SortByHeight;
+    } else {
+        mode = SortByWidth;
+    }
+
+    // sort rects into order
+    QList<int> newRectsSorted = sortRects(mode, newRectsMap);
+
+    // initialize anchor points
+    initAnchors();
+
+    // go through existing rects
+    bool ok = checkExistingRects(existingRects);
+    if (!ok) {
+        return QList<QRectF>();
+    }
+
+    QList<int> newRectsNotCalculated;
+    QList<QRectF> newExistingRects;
+    newExistingRects += existingRects;
+
+    // get positions for all new rects
+    for (int i = 0; i < newRectsMap.count(); i++) {
+        // proceed in sorted order with the rects
+        QRectF newRect = newRectsMap.value(newRectsSorted.at(i));
+        // find first free anchor point for rect
+        QPointF position = getPosition(newRect.size());
+        if (position != QPointF(-1,-1)) {
+            QRectF calculatedGeometry = QRectF(position.x() + mContentArea.x(),
+                                               position.y() + mContentArea.y(),
+                                               newRect.width(), newRect.height());
+            // update new rect instead of old one based on id map
+            newRectsMap.insert(newRectsSorted.at(i), calculatedGeometry);
+            // update existing rects
+            newExistingRects << calculatedGeometry;
+            // mark new rect reserved
+            bool marked = markAnchors(QRectF(position, newRect.size()));
+            if (!marked) {
+                return QList<QRectF>();
+            }
+
+        } else {
+            // collect rect that do not fit
+            newRectsNotCalculated << newRectsSorted.at(i);
+        }
+    }
+
+    // use center algorithm with offset for the rest rects that did not fit to content area
+    if (newRectsNotCalculated.count() > 0 ) {
+        // collect not organized rects
+        QList<QRectF> undoneRects;
+        for (int i = 0; i < newRectsNotCalculated.count(); i++) {
+            undoneRects << newRectsMap.value(newRectsNotCalculated.at(i));
+        }
+        QList<QRectF> calculatedRects =
+            mCenterAlgorithm->convert(mContentArea, newExistingRects, undoneRects, QPointF());
+        // update the rest rects instead of old ones
+        for (int i = 0; i < calculatedRects.count(); i++) {
+            newRectsMap.insert(newRectsNotCalculated.at(i), calculatedRects.at(i));
+            /* take rect out of list and add it to the end of the list
+               rect that do not fit are added in the end in users add order */
+            // we need to map z values to correct widgets to enable this
+           /*
+            newRectsMap.take(newRectsNotCalculated.at(i));
+            newRectsMap.insert(newRectsMap.count() + i + 1, calculatedRects.at(i));
+           */
+        }
+    }
+
+    return newRectsMap.values();
+}
+
+/*!    
+    Initializes anchor point network for area size
+*/
+void HsWidgetOrganizer::initAnchors()
+{
+    // need to zero just in case (if algorithm is called twice)
+    mAnchors = QList<bool>();
+    // create anchor point network
+    for (int i = 0; i < (mAnchorColumns * mAnchorRows); i++) {
+        mAnchors.append(false);
+    }
+}
+
+/*!    
+    Check existing rects and marks them reserved
+*/
+bool HsWidgetOrganizer::checkExistingRects(const QList<QRectF> &existingRects)
+{
+    foreach (QRectF rect, existingRects) {
+        /* if existing rect is on the edges of content area
+           need to drop one pixels because there is no anchors on the edge */
+        int rightmostPoint = rect.x() + rect.width();
+        if (rightmostPoint == mContentArea.width()) {
+            rect.setWidth(rect.width() - 1);
+        }
+        int undermostPoint = rect.y() + rect.height();
+        if (undermostPoint == mContentArea.height()) {
+            rect.setHeight(rect.height() - 1);
+        }
+        // decrease content area size in case it does not start from (0,0)
+        rect = QRectF(
+            QPointF(rect.x() - mContentArea.x(), rect.y() - mContentArea.y()),
+            rect.size());
+        bool marked = markAnchors(rect);
+        if (!marked) {
+            return false;
+        }
+    }
+    return true;
+}
+
+/*!    
+    Calculates pixel length as anchor points
+*/
+int HsWidgetOrganizer::convertToAnchors(int length)
+{
+    // calculate remainder
+    int remainder = length % mAnchorDistance;
+    // calculate anchor points (only pixel integrals are counted, decimals are cut away)
+    int anchorPoints = (length - remainder) / mAnchorDistance;
+    return anchorPoints;
+}
+
+/*!    
+    Marks reserved anchor points based on given rects
+*/
+bool HsWidgetOrganizer::markAnchors(const QRectF &rect)
+{
+    // in case content does not start from zero, need take contentArea into calculations
+    int startWidth = convertToAnchors(rect.x());
+    int endWidth = convertToAnchors(rect.x() + rect.width());
+    int startHeight = convertToAnchors(rect.y());
+    int endHeight = convertToAnchors(rect.y() + rect.height());
+
+    // mark reserved anchors row by row from left to right
+    for (int i = startWidth; i <= endWidth; i++) {
+        for (int j = startHeight; j <= endHeight; j++) {
+            int index = getAnchorListIndex(QPointF(i,j));
+            if (index < 0) {
+                return false;
+            }
+            mAnchors[index] = true;
+        }
+    }
+    return true;
+}
+
+/*!    
+    Returns anchor's list index based on given position
+*/
+int HsWidgetOrganizer::getAnchorListIndex(const QPointF &position)
+{
+    int index = (position.y() * mAnchorColumns) + position.x();
+    if (index < mAnchors.count()) {
+        return index;
+    } else {
+        return -1;
+    }
+}
+
+/*!    
+    Finds anchor points for content size
+*/
+QPointF HsWidgetOrganizer::getPosition(const QSizeF &size)
+{
+    QPointF startPoint(0,0);
+    // convert units from pixels to anchors
+    int width = convertToAnchors(size.width());
+    int height = convertToAnchors(size.height());
+
+    // based on search sequence, select position searching method
+    if (mSequence == HsConfiguration::SearchRowByRow) {
+        startPoint = searchPositionRowByRow(startPoint, width, height);
+    } else {
+        startPoint = searchPositionColumnByColumn(startPoint, width, height);
+    }
+
+    if (startPoint == QPointF(-1,-1)) {
+        return startPoint;
+    } else {
+        // return the actual pixel coordinate
+        return QPointF(startPoint.x() * mAnchorDistance, startPoint.y() * mAnchorDistance);
+    }
+}
+
+/*!    
+    Search sequence that finds anchor position by looking first for width on x-axis and
+    then secondarily on height from y-axis
+*/
+QPointF HsWidgetOrganizer::searchPositionRowByRow(QPointF startPoint, int width, int height)
+{
+    bool anchorFound = false;
+    QPointF candidatePoint(0,0);
+    // loop until anchor point is found
+    while (anchorFound == false) {
+        // search for width on specified row
+        candidatePoint = searchSpace(SearchRow, startPoint, width);
+        if (candidatePoint != QPointF(-1,-1)) {
+            // update start point to where found width starts
+            startPoint.setX(candidatePoint.x());
+            // check all anchor height points corresponding the found free width points
+            for(int i = startPoint.x(); i <= startPoint.x() + width; i++) {
+                // save current start point to be checked
+                QPointF point = QPointF(i, startPoint.y());
+                // search for height on specified column
+                candidatePoint = searchSpace(SearchColumn, point, height);
+                if (candidatePoint == QPointF(-1,-1)) {
+                    // update x anchor index
+                    startPoint.setX(startPoint.x() + 1);
+                    // set i to max to stop searching
+                    i = startPoint.x() + width;
+                }
+            }
+            // if all height searches were successfull
+            if (candidatePoint != QPointF(-1,-1)) {
+                anchorFound = true;
+            }
+        } else {
+            // update x and y start positions when row has been checked
+            startPoint.setX(0);
+            startPoint.setY(startPoint.y() + 1);
+            // check that enough height left
+            if (startPoint.y() >= mAnchorRows) {
+                return QPointF(-1,-1);
+            }
+        }
+    }
+
+    return startPoint;
+}
+
+/*!    
+    Search sequence that finds anchor position by looking first for height on y-axis and
+    then secondarily on width from x-axis
+*/
+QPointF HsWidgetOrganizer::searchPositionColumnByColumn(QPointF startPoint,
+                                                        int width, int height)
+{
+    bool anchorFound = false;
+    QPointF candidatePoint(0,0);
+
+    while (anchorFound == false) {
+        candidatePoint = searchSpace(SearchColumn, startPoint, height);
+        if (candidatePoint != QPointF(-1,-1)) {
+            startPoint.setY(candidatePoint.y());
+            for(int i = startPoint.y(); i <= startPoint.y() + height; i++) {
+                QPointF point = QPointF(startPoint.x(), i);
+                candidatePoint = searchSpace(SearchRow, point, width);
+                if (candidatePoint == QPointF(-1,-1)) {
+                    startPoint.setY(startPoint.y() + 1);
+                    i = startPoint.y() + height;
+                }
+            }
+            if (candidatePoint != QPointF(-1,-1)) {
+                anchorFound = true;
+            }
+        } else {
+            startPoint.setY(0);
+            startPoint.setX(startPoint.x() + 1);
+            if (startPoint.x() >= mAnchorColumns) {
+                return QPointF(-1,-1);
+            }
+        }
+    }
+
+    return startPoint;
+}
+
+/*!    
+    Searches anchor point space for given length
+*/
+QPointF HsWidgetOrganizer::searchSpace(SearchMode mode, QPointF startPoint, int length)
+{
+    int availableLength = 0;
+    // convert start point to an index in anchor list
+    int startIndex = getAnchorListIndex(startPoint);
+    int increment = 0;
+    int endIndex = 0;
+
+    // set end anchor index depending on checked axis
+    if (mode == SearchRow) {
+        // save the last index of the checked row
+        endIndex = getAnchorListIndex(QPointF((mAnchorColumns - 1), startPoint.y()));
+
+    } else {
+        // save the last index of the checked column
+        endIndex = getAnchorListIndex(QPointF(startPoint.x(), (mAnchorRows - 1)));
+        // we need to add increment due to anchors are listed row by row
+        increment = mAnchorColumns - 1;
+    }
+
+    // safety checks
+    if (startIndex == -1 || endIndex == -1) {
+        return QPointF(-1,-1);
+    }
+
+    // loop through anchor indexes, increment is added only when going through height
+    for (int i = startIndex; i <= endIndex; i = i + 1 + increment) {
+        // if anchor reserved
+        if (mAnchors.at(i) == true) {
+            availableLength = 0;
+            // if going through the first part of sequence (width/height)
+            if ((mSequence == HsConfiguration::SearchRowByRow && mode == SearchRow) ||
+                (mSequence == HsConfiguration::SearchColumnByColumn && mode == SearchColumn)) {
+                // update start index
+                startIndex = i + 1 + increment;
+            } else {
+                // exit immediately if second part of sequence fails
+                return QPointF(-1,-1);                
+            }
+        } else {
+            // if enough space found
+            if (availableLength == length) {
+                // return the actual anchor position
+                return getAnchorCoordinates(startIndex);
+            }
+            // update available length
+            availableLength++;
+        }
+    }
+
+    return QPointF(-1,-1);
+}
+
+/*!    
+    Returns pixel coordinate based on anchor coordinate
+*/
+QPointF HsWidgetOrganizer::getAnchorCoordinates(int index)
+{
+    if (index < mAnchors.count()) {
+        int x = index % mAnchorColumns;
+        int y = (index - x) / mAnchorColumns;
+        return QPointF(x,y);
+    } else {
+        return QPointF();
+    }
+}
+
+/*!
+    Sorts rects based on sort mode and given map of rects
+*/
+QList<int> HsWidgetOrganizer::sortRects(SortMode mode, const QMap<int, QRectF> &rects)
+{
+    QList<int> sortedRects;
+    int i = 0;
+    // loop through all rects
+    QMapIterator<int, QRectF> id(rects);
+    while (id.hasNext()) {
+        id.next();
+        int index = 0;
+        // add first rect id to sorted list
+        if (i == 0) {
+            sortedRects << id.key();
+        } else {
+            // go through existing rects in the sorted list
+            for ( int j = 0; j < sortedRects.count(); j++) {
+                // calculations for sortByArea
+                qreal existingArea = rects.value(sortedRects.at(j)).width() *
+                                                 rects.value(sortedRects.at(j)).height();
+                qreal newArea = id.value().width() * id.value().height();
+                // sort rects in height order
+                switch (mode) {
+                case SortByHeight:
+                    /* if rect heigth is smaller on already
+                       existing ones in the list -> increment index
+                    */
+                    if (id.value().height() <= rects.value(sortedRects.at(j)).height()) {
+                        index++;
+                    }
+                    break;
+                // sort rects in width order
+                case SortByWidth:
+                    // if rect width is smaller -> increment index
+                    if (id.value().width() <= rects.value(sortedRects.at(j)).width()) {
+                        index++;
+                    }
+                    break;
+                case SortByArea:
+                    // if rect area is smaller -> increment index
+                    if (newArea <= existingArea) {
+                        index++;
+                    }
+                // otherwise return in original order
+                default:
+                    index++;
+                    break;
+                }
+            }
+            // add rect id in the sorted list
+            sortedRects.insert(index, id.key());
+        }
+        i++;
+    }
+    return sortedRects;
+}
+
+#ifdef COVERAGE_MEASUREMENT
+#pragma CTC ENDSKIP
+#endif //COVERAGE_MEASUREMENT
+