<commit_before>/*
    This file is part of KOrganizer.

    Copyright (c) 2003 Cornelius Schumacher <schumacher@kde.org>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

    As a special exception, permission is given to link this program
    with any edition of Qt, and distribute the resulting executable,
    without including the source code for Qt in the source distribution.
*/

#include "urihandler.h"

#ifndef KORG_NODCOP
#include <dcopclient.h>
#include "kmailIface_stub.h"
#endif

#include <kiconloader.h>
#include <krun.h>
#include <kapplication.h>
#include <kprocess.h>
#include <kdebug.h>

bool UriHandler::process( const QString &uri )
{
  kdDebug(5850) << "UriHandler::process(): " << uri << endl;

#ifndef KORG_NODCOP
  if ( uri.startsWith( "kmail:" ) ) {
    // make sure kmail is running or the part is shown
    kapp->startServiceByDesktopPath("kmail");

    // parse string, show
    int colon = uri.find( ':' );
    // extract 'number' from 'kmail:<number>/<id>'
    QString serialNumberStr = uri.mid( colon + 1 );
    serialNumberStr = serialNumberStr.left( serialNumberStr.find( '/' ) );

    KMailIface_stub kmailIface( "kmail", "KMailIface" );
    kmailIface.showMail( serialNumberStr.toUInt(), QString() );
    return true;
  } else if ( uri.startsWith( "mailto:" ) ) {
    KApplication::kApplication()->invokeMailer( uri.mid(7), QString::null );
    return true;
  } else if ( uri.startsWith( "uid:" ) ) {
    DCOPClient *client = KApplication::kApplication()->dcopClient();
    const QByteArray noParamData;
    const QByteArray paramData;
    QByteArray replyData;
    QCString replyTypeStr;
    bool foundAbbrowser = client->call( "kaddressbook", "KAddressBookIface",
                                        "interfaces()",  noParamData,
                                        replyTypeStr, replyData );
    if ( foundAbbrowser ) {
      //KAddressbook is already running, so just DCOP to it to bring up the contact editor
#if KDE_IS_VERSION( 3, 2, 90 )
      kapp->updateRemoteUserTimestamp("kaddressbook");
#endif
      DCOPRef kaddressbook( "kaddressbook", "KAddressBookIface" );
      kaddressbook.send( "showContactEditor", uri.mid( 6 ) );
      return true;
    } else {
      /*
        KaddressBook is not already running.  Pass it the UID of the contact via the command line while starting it - its neater.
        We start it without its main interface
      */
      KIconLoader *iconLoader = new KIconLoader();
      QString iconPath = iconLoader->iconPath( "go", KIcon::Small );
      delete iconLoader;
      QString tmpStr = "kaddressbook --editor-only --uid ";
      tmpStr += KProcess::quote( uri.mid( 6 ) );
      KRun::runCommand( tmpStr, "KAddressBook", iconPath );
      return true;
    }
  }
  else {  // no special URI, let KDE handle it
    new KRun(KURL( uri ));
  }
#endif

  return false;
}
<commit_msg>This should be the global iconloader anyway.<commit_after>/*
    This file is part of KOrganizer.

    Copyright (c) 2003 Cornelius Schumacher <schumacher@kde.org>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

    As a special exception, permission is given to link this program
    with any edition of Qt, and distribute the resulting executable,
    without including the source code for Qt in the source distribution.
*/

#include "urihandler.h"

#ifndef KORG_NODCOP
#include <dcopclient.h>
#include "kmailIface_stub.h"
#endif

#include <kiconloader.h>
#include <krun.h>
#include <kapplication.h>
#include <kprocess.h>
#include <kdebug.h>

bool UriHandler::process( const QString &uri )
{
  kdDebug(5850) << "UriHandler::process(): " << uri << endl;

#ifndef KORG_NODCOP
  if ( uri.startsWith( "kmail:" ) ) {
    // make sure kmail is running or the part is shown
    kapp->startServiceByDesktopPath("kmail");

    // parse string, show
    int colon = uri.find( ':' );
    // extract 'number' from 'kmail:<number>/<id>'
    QString serialNumberStr = uri.mid( colon + 1 );
    serialNumberStr = serialNumberStr.left( serialNumberStr.find( '/' ) );

    KMailIface_stub kmailIface( "kmail", "KMailIface" );
    kmailIface.showMail( serialNumberStr.toUInt(), QString() );
    return true;
  } else if ( uri.startsWith( "mailto:" ) ) {
    KApplication::kApplication()->invokeMailer( uri.mid(7), QString::null );
    return true;
  } else if ( uri.startsWith( "uid:" ) ) {
    DCOPClient *client = KApplication::kApplication()->dcopClient();
    const QByteArray noParamData;
    const QByteArray paramData;
    QByteArray replyData;
    QCString replyTypeStr;
    bool foundAbbrowser = client->call( "kaddressbook", "KAddressBookIface",
                                        "interfaces()",  noParamData,
                                        replyTypeStr, replyData );
    if ( foundAbbrowser ) {
      //KAddressbook is already running, so just DCOP to it to bring up the contact editor
#if KDE_IS_VERSION( 3, 2, 90 )
      kapp->updateRemoteUserTimestamp("kaddressbook");
#endif
      DCOPRef kaddressbook( "kaddressbook", "KAddressBookIface" );
      kaddressbook.send( "showContactEditor", uri.mid( 6 ) );
      return true;
    } else {
      /*
        KaddressBook is not already running.  Pass it the UID of the contact via the command line while starting it - its neater.
        We start it without its main interface
      */
      QString iconPath = KGlobal::iconLoader()->iconPath( "go", KIcon::Small );
      QString tmpStr = "kaddressbook --editor-only --uid ";
      tmpStr += KProcess::quote( uri.mid( 6 ) );
      KRun::runCommand( tmpStr, "KAddressBook", iconPath );
      return true;
    }
  }
  else {  // no special URI, let KDE handle it
    new KRun(KURL( uri ));
  }
#endif

  return false;
}
<|endoftext|>


<commit_before>#pragma once

namespace principia {
namespace ksp_plugin {

// A boolean which is constructed true and becomes false when |Flop| is called.
class Monostable {
 public:
  Monostable() = default;
  ~Monostable() = default;

  Monostable(Monostable const&) = delete;
  Monostable(Monostable&&) = delete;;
  Monostable& operator=(Monostable const&) = delete;
  Monostable& operator=(Monostable&&) = delete;

  void Flop();
  operator bool() const;

 private:
  bool transient_ = true;
};

}  // namespace ksp_plugin
}  // namespace principia
<commit_msg>Why didn't the linter catch that?<commit_after>#pragma once

namespace principia {
namespace ksp_plugin {

// A boolean which is constructed true and becomes false when |Flop| is called.
class Monostable {
 public:
  Monostable() = default;
  ~Monostable() = default;

  Monostable(Monostable const&) = delete;
  Monostable(Monostable&&) = delete;
  Monostable& operator=(Monostable const&) = delete;
  Monostable& operator=(Monostable&&) = delete;

  void Flop();
  operator bool() const;

 private:
  bool transient_ = true;
};

}  // namespace ksp_plugin
}  // namespace principia
<|endoftext|>


<commit_before>#include "text_blob.h"
#include "wx/dcgraph.h"
#include "wx/graphics.h"
#include "wx/tokenzr.h"

#include <iostream>
#include <algorithm>

wxTextBlob::wxTextBlob()
    : m_TextExtentContext{wxGraphicsContext::Create()}
    , m_GlyphAdvanceX {0}
    , m_LineHeight {0}
    , m_TextParts {} {
}

wxString wxTextBlob::wxFontToFCDesc(const wxFont * pFont) {
    wxString desc(pFont->GetFaceName());

    desc << ":size=" << pFont->GetPointSize();

    switch (pFont->GetStyle()) {
    case wxFONTSTYLE_ITALIC:
    case wxFONTSTYLE_SLANT:
         desc << ":slant=italic";
         break;
    default:
        break;
    }

    switch(pFont->GetWeight()){
    case wxFONTWEIGHT_BOLD:
        desc << ":weight=200";
        break;
    case wxFONTWEIGHT_LIGHT:
        desc << ":weight=50";
        break;
    default:
        break;
    }

    return desc;
}

wxPoint wxTextBlob::AddText(const wxString & text,
                         const wxPoint pt,
                         const wxFont * pFont,
                         wxColour fore_color,
                         wxColour back_color) {
    (void)text;
    (void)pt;
    (void)pFont;
    (void)fore_color;
    (void)back_color;

    m_TextExtentContext->SetFont(*pFont, fore_color);

    std::cout << "font:" << pFont->GetNativeFontInfoUserDesc() << "," << pFont->GetPointSize() << "," << pFont->GetPixelSize().y << pFont->GetWeight()
              << wxFontToFCDesc(pFont) << std::endl;

    wxStringTokenizer tokenizer(text, "\n", wxTOKEN_RET_EMPTY_ALL);

    wxPoint tmpPt{pt}, lastPt;

    bool empty_token = false;

    do {
        auto token = tokenizer.HasMoreTokens() ? tokenizer.GetNextToken() : text;

        empty_token = token.IsEmpty();

        wxDouble width = 0.0, height = 0.0, leading = 0.0;

        m_TextExtentContext->GetTextExtent(empty_token ? " " : token,
                                           &width,
                                           &height,
                                           nullptr,
                                           &leading);

        if (empty_token) width = 0;

        m_TextParts.push_back(
            {token, tmpPt, pFont,
                        fore_color, back_color,
                {(int)width, (int)std::max(height + leading, (wxDouble)m_LineHeight)}
            });

        lastPt = tmpPt;
        lastPt.x += width;

        tmpPt.x = pt.x;
        tmpPt.y += std::max(height + leading, (wxDouble)m_LineHeight);
    } while(tokenizer.HasMoreTokens());

    return empty_token ? tmpPt : lastPt;
}

void wxTextBlob::Render(wxGraphicsContext * context) {
    (void)context;

    context->PushState();
    for(auto it = m_TextParts.begin(),
                it_end = m_TextParts.end();
        it != it_end;
        it++) {
        context->SetFont(*it->pFont, it->fore);

        if (it->back != wxNullColour) {
            context->DrawText(it->text, it->pt.x, it->pt.y, context->CreateBrush(wxBrush(it->back)));
        } else {
            context->DrawText(it->text, it->pt.x, it->pt.y);
        }
    }
    context->PopState();
}
<commit_msg>remove debug print<commit_after>#include "text_blob.h"
#include "wx/dcgraph.h"
#include "wx/graphics.h"
#include "wx/tokenzr.h"

#include <iostream>
#include <algorithm>

wxTextBlob::wxTextBlob()
    : m_TextExtentContext{wxGraphicsContext::Create()}
    , m_GlyphAdvanceX {0}
    , m_LineHeight {0}
    , m_TextParts {} {
}

wxString wxTextBlob::wxFontToFCDesc(const wxFont * pFont) {
    wxString desc(pFont->GetFaceName());

    desc << ":size=" << pFont->GetPointSize();

    switch (pFont->GetStyle()) {
    case wxFONTSTYLE_ITALIC:
    case wxFONTSTYLE_SLANT:
         desc << ":slant=italic";
         break;
    default:
        break;
    }

    switch(pFont->GetWeight()){
    case wxFONTWEIGHT_BOLD:
        desc << ":weight=200";
        break;
    case wxFONTWEIGHT_LIGHT:
        desc << ":weight=50";
        break;
    default:
        break;
    }

    return desc;
}

wxPoint wxTextBlob::AddText(const wxString & text,
                         const wxPoint pt,
                         const wxFont * pFont,
                         wxColour fore_color,
                         wxColour back_color) {
    (void)text;
    (void)pt;
    (void)pFont;
    (void)fore_color;
    (void)back_color;

    m_TextExtentContext->SetFont(*pFont, fore_color);

    wxStringTokenizer tokenizer(text, "\n", wxTOKEN_RET_EMPTY_ALL);

    wxPoint tmpPt{pt}, lastPt;

    bool empty_token = false;

    do {
        auto token = tokenizer.HasMoreTokens() ? tokenizer.GetNextToken() : text;

        empty_token = token.IsEmpty();

        wxDouble width = 0.0, height = 0.0, leading = 0.0;

        m_TextExtentContext->GetTextExtent(empty_token ? " " : token,
                                           &width,
                                           &height,
                                           nullptr,
                                           &leading);

        if (empty_token) width = 0;

        m_TextParts.push_back(
            {token, tmpPt, pFont,
                        fore_color, back_color,
                {(int)width, (int)std::max(height + leading, (wxDouble)m_LineHeight)}
            });

        lastPt = tmpPt;
        lastPt.x += width;

        tmpPt.x = pt.x;
        tmpPt.y += std::max(height + leading, (wxDouble)m_LineHeight);
    } while(tokenizer.HasMoreTokens());

    return empty_token ? tmpPt : lastPt;
}

void wxTextBlob::Render(wxGraphicsContext * context) {
    (void)context;

    context->PushState();
    for(auto it = m_TextParts.begin(),
                it_end = m_TextParts.end();
        it != it_end;
        it++) {
        context->SetFont(*it->pFont, it->fore);

        if (it->back != wxNullColour) {
            context->DrawText(it->text, it->pt.x, it->pt.y, context->CreateBrush(wxBrush(it->back)));
        } else {
            context->DrawText(it->text, it->pt.x, it->pt.y);
        }
    }
    context->PopState();
}
<|endoftext|>


<commit_before>//
// This file is part of the Marble Virtual Globe.
//
// This program is free software licensed under the GNU LGPL. You can
// find a copy of this license in LICENSE.txt in the top directory of
// the source code.
//
// Copyright 2010      Dennis Nienhüser <earthwings@gentoo.org>
//

#include "GoToDialog.h"
#include "MarbleWidget.h"
#include "MarbleModel.h"
#include "MarbleRunnerManager.h"
#include "MarblePlacemarkModel.h"
#include "GeoDataTreeModel.h"
#include "GeoDataDocument.h"
#include "GeoDataFolder.h"
#include "PositionTracking.h"
#include "BookmarkManager.h"
#include "routing/RoutingManager.h"
#include "routing/RouteRequest.h"

#include <QtCore/QAbstractListModel>
#include <QtCore/QTimer>
#include <QtGui/QPushButton>
#include <QtGui/QPainter>

namespace Marble
{

namespace
{
int const GeoDataLookAtRole = Qt::UserRole + 1;
}

class TargetModel : public QAbstractListModel
{
public:
    TargetModel( MarbleWidget* marbleWidget, QObject * parent = 0 );

    virtual int rowCount ( const QModelIndex & parent = QModelIndex() ) const;

    virtual QVariant data ( const QModelIndex & index, int role = Qt::DisplayRole ) const;

    void setShowRoutingItems( bool show );

private:
    QVariant currentLocationData ( int role ) const;

    QVariant routeData ( const QVector<GeoDataPlacemark> &via, int index, int role ) const;

    QVariant homeData ( int role ) const;

    QVariant bookmarkData ( int index, int role ) const;

    QVector<GeoDataPlacemark> viaPoints() const;

    MarbleWidget* m_marbleWidget;

    QVector<GeoDataPlacemark*> m_bookmarks;

    bool m_hasCurrentLocation;

    bool m_showRoutingItems;
};

class GoToDialogPrivate
{
public:
    GoToDialog* m_parent;

    MarbleWidget* m_marbleWidget;

    GeoDataLookAt m_lookAt;

    TargetModel* m_targetModel;

    MarbleRunnerManager* m_runnerManager;

    GeoDataDocument *m_document;

    GeoDataTreeModel m_placemarkModel;

    QTimer m_progressTimer;

    int m_currentFrame;

    QVector<QIcon> m_progressAnimation;

    GoToDialogPrivate( GoToDialog* parent, MarbleWidget* marbleWidget );

    void saveSelection( const QModelIndex &index );

    void createProgressAnimation();
};

TargetModel::TargetModel( MarbleWidget* marbleWidget, QObject * parent ) :
    QAbstractListModel( parent ),
    m_marbleWidget( marbleWidget ), m_hasCurrentLocation( false ),
    m_showRoutingItems( true )
{
    BookmarkManager* manager = marbleWidget->model()->bookmarkManager();
    foreach( GeoDataFolder * folder, manager->folders() ) {
        QVector<GeoDataPlacemark*> bookmarks = folder->placemarkList();
        QVector<GeoDataPlacemark*>::const_iterator iter = bookmarks.constBegin();
        QVector<GeoDataPlacemark*>::const_iterator end = bookmarks.constEnd();

        for ( ; iter != end; ++iter ) {
            m_bookmarks.push_back( *iter );
        }
    }

    PositionTracking* tracking = m_marbleWidget->model()->positionTracking();
    m_hasCurrentLocation = tracking && tracking->status() == PositionProviderStatusAvailable;
}

QVector<GeoDataPlacemark> TargetModel::viaPoints() const
{
    if ( !m_showRoutingItems ) {
        return QVector<GeoDataPlacemark>();
    }

    RouteRequest* request = m_marbleWidget->model()->routingManager()->routeRequest();
    QVector<GeoDataPlacemark> result;
    for ( int i = 0; i < request->size(); ++i ) {
        if ( request->at( i ).longitude() != 0.0 || request->at( i ).latitude() != 0.0 ) {
            GeoDataPlacemark placemark;
            placemark.setCoordinate( request->at( i ) );
            placemark.setName( request->name( i ) );
            result.push_back( placemark );
        }
    }
    return result;
}

int TargetModel::rowCount ( const QModelIndex & parent ) const
{
    int result = 0;
    if ( !parent.isValid() ) {
        result += m_hasCurrentLocation ? 1 : 0;
        result += viaPoints().size(); // route
        result += 1; // home location
        result += m_bookmarks.size(); // bookmarks
        return result;
    }

    return result;
}

QVariant TargetModel::currentLocationData ( int role ) const
{
    PositionTracking* tracking = m_marbleWidget->model()->positionTracking();
    if ( tracking && tracking->status() == PositionProviderStatusAvailable ) {
        GeoDataCoordinates currentLocation = tracking->currentLocation();
        switch( role ) {
        case Qt::DisplayRole: return tr( "Current Location: %1" ).arg( currentLocation.toString() ) ;
        case Qt::DecorationRole: return QIcon( ":/icons/gps.png" );
        case GeoDataLookAtRole: {
            GeoDataLookAt result;
            result.setCoordinates( currentLocation );
            // By happy coincidence this equals OpenStreetMap tile level 15
            result.setRange( 851.807 );
            return qVariantFromValue( result );
        }
        }
    }

    return QVariant();
}

QVariant TargetModel::routeData ( const QVector<GeoDataPlacemark> &via, int index, int role ) const
{
    RouteRequest* request = m_marbleWidget->model()->routingManager()->routeRequest();
    switch( role ) {
    case Qt::DisplayRole: return via.at( index ).name();
    case Qt::DecorationRole: return QIcon( request->pixmap( index ) );
    case GeoDataLookAtRole: {
        GeoDataLookAt result;
        result.setCoordinates( via.at( index ).coordinate() );
        // By happy coincidence this equals OpenStreetMap tile level 16
        result.setRange( 425.903 );
        return qVariantFromValue( result );
    }
    }

    return QVariant();
}

QVariant TargetModel::homeData ( int role ) const
{
    switch( role ) {
    case Qt::DisplayRole: return tr( "Home" );
    case Qt::DecorationRole: return QIcon( ":/icons/go-home.png" );
    case GeoDataLookAtRole: {
        qreal lon( 0.0 ), lat( 0.0 );
        int zoom( 0 );
        m_marbleWidget->model()->home( lon, lat, zoom );
        GeoDataLookAt result;
        result.setLongitude( lon, GeoDataCoordinates::Degree );
        result.setLatitude( lat, GeoDataCoordinates::Degree );
        result.setRange( m_marbleWidget->distanceFromZoom( zoom ) * KM2METER );
        return qVariantFromValue( result );
    }
    }

    return QVariant();
}

QVariant TargetModel::bookmarkData ( int index, int role ) const
{
    switch( role ) {
    case Qt::DisplayRole: {
        GeoDataFolder* folder = dynamic_cast<GeoDataFolder*>( m_bookmarks[index]->parent() );
        Q_ASSERT( folder && "Internal bookmark representation has changed. Please report this as a bug at http://bugs.kde.org." );
        if ( folder ) {
            return QString(folder->name() + QLatin1String(" / ") + m_bookmarks[index]->name());
        }
    }
    case Qt::DecorationRole: return QIcon( ":/icons/bookmarks.png" );
    case GeoDataLookAtRole: return qVariantFromValue( *m_bookmarks[index]->lookAt() );
    }

    return QVariant();
}

QVariant TargetModel::data ( const QModelIndex & index, int role ) const
{
    if ( index.isValid() && index.row() >= 0 && index.row() < rowCount() ) {
        int row = index.row();
        bool const isCurrentLocation = row == 0 && m_hasCurrentLocation;
        int homeOffset = m_hasCurrentLocation ? 1 : 0;
        QVector<GeoDataPlacemark> via = viaPoints();
        bool const isRoute = row >= homeOffset && row < homeOffset + via.size();

        if ( isCurrentLocation ) {
            return currentLocationData( role );
        } else if ( isRoute ) {
            int routeIndex = row - homeOffset;
            Q_ASSERT( routeIndex >= 0 && routeIndex < via.size() );
            return routeData( via, routeIndex, role );
        } else {
            int bookmarkIndex = row - homeOffset - via.size();
            if ( bookmarkIndex == 0 ) {
                return homeData( role );
            } else {
                --bookmarkIndex;
                Q_ASSERT( bookmarkIndex >= 0 && bookmarkIndex < m_bookmarks.size() );
                return bookmarkData( bookmarkIndex, role );
            }
        }
    }

    return QVariant();
}

void TargetModel::setShowRoutingItems( bool show )
{
    m_showRoutingItems = show;
    reset();
}

void GoToDialogPrivate::createProgressAnimation()
{
    bool const smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen;
    int const iconSize = smallScreen ? 32 : 16;

    // Size parameters
    qreal const h = iconSize / 2.0; // Half of the icon size
    qreal const q = h / 2.0; // Quarter of the icon size
    qreal const d = 7.5; // Circle diameter
    qreal const r = d / 2.0; // Circle radius

    // Canvas parameters
    QImage canvas( iconSize, iconSize, QImage::Format_ARGB32 );
    QPainter painter( &canvas );
    painter.setRenderHint( QPainter::Antialiasing, true );
    painter.setPen( QColor ( Qt::gray ) );
    painter.setBrush( QColor( Qt::white ) );

    // Create all frames
    for( double t = 0.0; t < 2 * M_PI; t += M_PI / 8.0 ) {
        canvas.fill( Qt::transparent );
        QRectF firstCircle( h - r + q * cos( t ), h - r + q * sin( t ), d, d );
        QRectF secondCircle( h - r + q * cos( t + M_PI ), h - r + q * sin( t + M_PI ), d, d );
        painter.drawEllipse( firstCircle );
        painter.drawEllipse( secondCircle );
        m_progressAnimation.push_back( QIcon( QPixmap::fromImage( canvas ) ) );
    }
}

GoToDialogPrivate::GoToDialogPrivate( GoToDialog* parent, MarbleWidget* marbleWidget ) :
    m_parent( parent), m_marbleWidget( marbleWidget ), m_targetModel( 0 ),
    m_runnerManager( 0 ), m_document( new GeoDataDocument ), m_currentFrame( 0 )
{
    m_progressTimer.setInterval( 100 );
}

void GoToDialogPrivate::saveSelection( const QModelIndex &index )
{
    if ( m_parent->searchButton->isChecked() && m_document->size() ) {
        QVariant coordinates = m_placemarkModel.data( index, MarblePlacemarkModel::CoordinateRole );
        m_lookAt = GeoDataLookAt();
        m_lookAt.setCoordinates( qVariantValue<GeoDataCoordinates>( coordinates ) );
        // By happy coincidence this equals OpenStreetMap tile level 16
        m_lookAt.setRange( 425.903 );
    } else {
        QVariant data = index.data( GeoDataLookAtRole );
        m_lookAt = qVariantValue<GeoDataLookAt>( data );
    }
    m_parent->accept();
}

void GoToDialog::startSearch()
{
    QString const searchTerm = searchLineEdit->text().trimmed();
    if ( searchTerm.isEmpty() ) {
        return;
    }

    if ( !d->m_runnerManager ) {
        d->m_runnerManager = new MarbleRunnerManager( d->m_marbleWidget->model()->pluginManager(), this );
        d->m_runnerManager->setModel( d->m_marbleWidget->model() );
        connect( d->m_runnerManager, SIGNAL( searchResultChanged( QVector<GeoDataPlacemark*> ) ),
                 this, SLOT( updateSearchResult( QVector<GeoDataPlacemark*> ) ) );
        connect( d->m_runnerManager, SIGNAL( searchFinished( QString ) ),
                this, SLOT( stopProgressAnimation() ) );
    }

    d->m_runnerManager->findPlacemarks( searchTerm );
    if ( d->m_progressAnimation.isEmpty() ) {
        d->createProgressAnimation();
    }
    d->m_progressTimer.start();
    progressButton->setVisible( true );
    searchLineEdit->setEnabled( false );
    updateResultMessage( 0 );
}

void GoToDialog::updateSearchResult( QVector<GeoDataPlacemark*> placemarks )
{
    d->m_placemarkModel.setRootDocument( 0 );
    d->m_document->clear();
    foreach (GeoDataPlacemark *placemark, placemarks) {
        d->m_document->append( new GeoDataPlacemark( *placemark ) );
    }
    d->m_placemarkModel.setRootDocument( d->m_document );
    bookmarkListView->setModel( &d->m_placemarkModel );
    updateResultMessage( d->m_placemarkModel.rowCount() );
}

GoToDialog::GoToDialog( MarbleWidget* marbleWidget, QWidget * parent, Qt::WindowFlags flags ) :
    QDialog( parent, flags ), d( new GoToDialogPrivate( this, marbleWidget ) )
{
#ifdef Q_WS_MAEMO_5
        setAttribute( Qt::WA_Maemo5StackedWindow );
        setWindowFlags( Qt::Window );
#endif // Q_WS_MAEMO_5
    setupUi( this );

#if QT_VERSION >= 0x40700
    searchLineEdit->setPlaceholderText( tr( "Address or search term" ) );
#endif

    d->m_targetModel = new TargetModel( marbleWidget, this );
    d->m_placemarkModel.setRootDocument( d->m_document );
    bookmarkListView->setModel( d->m_targetModel );
    connect( bookmarkListView, SIGNAL( activated( QModelIndex ) ),
             this, SLOT( saveSelection ( QModelIndex ) ) );
    connect( searchLineEdit, SIGNAL( returnPressed() ),
             this, SLOT( startSearch() ) );
    buttonBox->button( QDialogButtonBox::Close )->setAutoDefault( false );
    connect( searchButton, SIGNAL( clicked( bool ) ),
             this, SLOT( updateSearchMode() ) );
    connect( browseButton, SIGNAL( clicked( bool ) ),
             this, SLOT( updateSearchMode() ) );
    connect( &d->m_progressTimer, SIGNAL( timeout() ),
             this, SLOT( updateProgress() ) );
    connect( progressButton, SIGNAL( clicked( bool ) ),
             this, SLOT( stopProgressAnimation() ) );
    updateSearchMode();
    progressButton->setVisible( false );
}

GoToDialog::~GoToDialog()
{
    delete d;
}

GeoDataLookAt GoToDialog::lookAt() const
{
    return d->m_lookAt;
}

void GoToDialog::setShowRoutingItems( bool show )
{
    d->m_targetModel->setShowRoutingItems( show );
}

void GoToDialog::setSearchEnabled( bool enabled )
{
    browseButton->setVisible( enabled );
    searchButton->setVisible( enabled );
    if ( !enabled ) {
        searchButton->setChecked( false );
        updateSearchMode();
    }
}

void GoToDialog::updateSearchMode()
{
    bool const searchEnabled = searchButton->isChecked();
    searchLineEdit->setVisible( searchEnabled );
    descriptionLabel->setVisible( searchEnabled );
    progressButton->setVisible( searchEnabled && d->m_progressTimer.isActive() );
    if ( searchEnabled ) {
        bookmarkListView->setModel( &d->m_placemarkModel );
    } else {
        bookmarkListView->setModel( d->m_targetModel );
    }
}

void GoToDialog::updateProgress()
{
    if ( !d->m_progressAnimation.isEmpty() ) {
        d->m_currentFrame = ( d->m_currentFrame + 1 ) % d->m_progressAnimation.size();
        QIcon frame = d->m_progressAnimation[d->m_currentFrame];
        progressButton->setIcon( frame );
    }
}

void GoToDialog::stopProgressAnimation()
{
    searchLineEdit->setEnabled( true );
    d->m_progressTimer.stop();
    updateResultMessage( bookmarkListView->model()->rowCount() );
    progressButton->setVisible( false );
}

void GoToDialog::updateResultMessage( int results )
{
    descriptionLabel->setText( tr( "%n results found.", "Number of search results", results ) );
}

}

#include "GoToDialog.moc"
<commit_msg>GoToDialog: more precise member names<commit_after>//
// This file is part of the Marble Virtual Globe.
//
// This program is free software licensed under the GNU LGPL. You can
// find a copy of this license in LICENSE.txt in the top directory of
// the source code.
//
// Copyright 2010      Dennis Nienhüser <earthwings@gentoo.org>
// Copyright 2011      Bernhard Beschow <bbeschow@cs.tu-berlin.de>
//

#include "GoToDialog.h"
#include "MarbleWidget.h"
#include "MarbleModel.h"
#include "MarbleRunnerManager.h"
#include "MarblePlacemarkModel.h"
#include "GeoDataTreeModel.h"
#include "GeoDataDocument.h"
#include "GeoDataFolder.h"
#include "PositionTracking.h"
#include "BookmarkManager.h"
#include "routing/RoutingManager.h"
#include "routing/RouteRequest.h"

#include <QtCore/QAbstractListModel>
#include <QtCore/QTimer>
#include <QtGui/QPushButton>
#include <QtGui/QPainter>

namespace Marble
{

namespace
{
int const GeoDataLookAtRole = Qt::UserRole + 1;
}

class TargetModel : public QAbstractListModel
{
public:
    TargetModel( MarbleWidget* marbleWidget, QObject * parent = 0 );

    virtual int rowCount ( const QModelIndex & parent = QModelIndex() ) const;

    virtual QVariant data ( const QModelIndex & index, int role = Qt::DisplayRole ) const;

    void setShowRoutingItems( bool show );

private:
    QVariant currentLocationData ( int role ) const;

    QVariant routeData ( const QVector<GeoDataPlacemark> &via, int index, int role ) const;

    QVariant homeData ( int role ) const;

    QVariant bookmarkData ( int index, int role ) const;

    QVector<GeoDataPlacemark> viaPoints() const;

    MarbleWidget* m_marbleWidget;

    QVector<GeoDataPlacemark*> m_bookmarks;

    bool m_hasCurrentLocation;

    bool m_showRoutingItems;
};

class GoToDialogPrivate
{
public:
    GoToDialog* m_parent;

    MarbleWidget* m_marbleWidget;

    GeoDataLookAt m_lookAt;

    TargetModel m_targetModel;

    MarbleRunnerManager* m_runnerManager;

    GeoDataDocument *m_searchResult;

    GeoDataTreeModel m_searchResultModel;

    QTimer m_progressTimer;

    int m_currentFrame;

    QVector<QIcon> m_progressAnimation;

    GoToDialogPrivate( GoToDialog* parent, MarbleWidget* marbleWidget );

    void saveSelection( const QModelIndex &index );

    void createProgressAnimation();
};

TargetModel::TargetModel( MarbleWidget* marbleWidget, QObject * parent ) :
    QAbstractListModel( parent ),
    m_marbleWidget( marbleWidget ), m_hasCurrentLocation( false ),
    m_showRoutingItems( true )
{
    BookmarkManager* manager = marbleWidget->model()->bookmarkManager();
    foreach( GeoDataFolder * folder, manager->folders() ) {
        QVector<GeoDataPlacemark*> bookmarks = folder->placemarkList();
        QVector<GeoDataPlacemark*>::const_iterator iter = bookmarks.constBegin();
        QVector<GeoDataPlacemark*>::const_iterator end = bookmarks.constEnd();

        for ( ; iter != end; ++iter ) {
            m_bookmarks.push_back( *iter );
        }
    }

    PositionTracking* tracking = m_marbleWidget->model()->positionTracking();
    m_hasCurrentLocation = tracking && tracking->status() == PositionProviderStatusAvailable;
}

QVector<GeoDataPlacemark> TargetModel::viaPoints() const
{
    if ( !m_showRoutingItems ) {
        return QVector<GeoDataPlacemark>();
    }

    RouteRequest* request = m_marbleWidget->model()->routingManager()->routeRequest();
    QVector<GeoDataPlacemark> result;
    for ( int i = 0; i < request->size(); ++i ) {
        if ( request->at( i ).longitude() != 0.0 || request->at( i ).latitude() != 0.0 ) {
            GeoDataPlacemark placemark;
            placemark.setCoordinate( request->at( i ) );
            placemark.setName( request->name( i ) );
            result.push_back( placemark );
        }
    }
    return result;
}

int TargetModel::rowCount ( const QModelIndex & parent ) const
{
    int result = 0;
    if ( !parent.isValid() ) {
        result += m_hasCurrentLocation ? 1 : 0;
        result += viaPoints().size(); // route
        result += 1; // home location
        result += m_bookmarks.size(); // bookmarks
        return result;
    }

    return result;
}

QVariant TargetModel::currentLocationData ( int role ) const
{
    PositionTracking* tracking = m_marbleWidget->model()->positionTracking();
    if ( tracking && tracking->status() == PositionProviderStatusAvailable ) {
        GeoDataCoordinates currentLocation = tracking->currentLocation();
        switch( role ) {
        case Qt::DisplayRole: return tr( "Current Location: %1" ).arg( currentLocation.toString() ) ;
        case Qt::DecorationRole: return QIcon( ":/icons/gps.png" );
        case GeoDataLookAtRole: {
            GeoDataLookAt result;
            result.setCoordinates( currentLocation );
            // By happy coincidence this equals OpenStreetMap tile level 15
            result.setRange( 851.807 );
            return qVariantFromValue( result );
        }
        }
    }

    return QVariant();
}

QVariant TargetModel::routeData ( const QVector<GeoDataPlacemark> &via, int index, int role ) const
{
    RouteRequest* request = m_marbleWidget->model()->routingManager()->routeRequest();
    switch( role ) {
    case Qt::DisplayRole: return via.at( index ).name();
    case Qt::DecorationRole: return QIcon( request->pixmap( index ) );
    case GeoDataLookAtRole: {
        GeoDataLookAt result;
        result.setCoordinates( via.at( index ).coordinate() );
        // By happy coincidence this equals OpenStreetMap tile level 16
        result.setRange( 425.903 );
        return qVariantFromValue( result );
    }
    }

    return QVariant();
}

QVariant TargetModel::homeData ( int role ) const
{
    switch( role ) {
    case Qt::DisplayRole: return tr( "Home" );
    case Qt::DecorationRole: return QIcon( ":/icons/go-home.png" );
    case GeoDataLookAtRole: {
        qreal lon( 0.0 ), lat( 0.0 );
        int zoom( 0 );
        m_marbleWidget->model()->home( lon, lat, zoom );
        GeoDataLookAt result;
        result.setLongitude( lon, GeoDataCoordinates::Degree );
        result.setLatitude( lat, GeoDataCoordinates::Degree );
        result.setRange( m_marbleWidget->distanceFromZoom( zoom ) * KM2METER );
        return qVariantFromValue( result );
    }
    }

    return QVariant();
}

QVariant TargetModel::bookmarkData ( int index, int role ) const
{
    switch( role ) {
    case Qt::DisplayRole: {
        GeoDataFolder* folder = dynamic_cast<GeoDataFolder*>( m_bookmarks[index]->parent() );
        Q_ASSERT( folder && "Internal bookmark representation has changed. Please report this as a bug at http://bugs.kde.org." );
        if ( folder ) {
            return QString(folder->name() + QLatin1String(" / ") + m_bookmarks[index]->name());
        }
    }
    case Qt::DecorationRole: return QIcon( ":/icons/bookmarks.png" );
    case GeoDataLookAtRole: return qVariantFromValue( *m_bookmarks[index]->lookAt() );
    }

    return QVariant();
}

QVariant TargetModel::data ( const QModelIndex & index, int role ) const
{
    if ( index.isValid() && index.row() >= 0 && index.row() < rowCount() ) {
        int row = index.row();
        bool const isCurrentLocation = row == 0 && m_hasCurrentLocation;
        int homeOffset = m_hasCurrentLocation ? 1 : 0;
        QVector<GeoDataPlacemark> via = viaPoints();
        bool const isRoute = row >= homeOffset && row < homeOffset + via.size();

        if ( isCurrentLocation ) {
            return currentLocationData( role );
        } else if ( isRoute ) {
            int routeIndex = row - homeOffset;
            Q_ASSERT( routeIndex >= 0 && routeIndex < via.size() );
            return routeData( via, routeIndex, role );
        } else {
            int bookmarkIndex = row - homeOffset - via.size();
            if ( bookmarkIndex == 0 ) {
                return homeData( role );
            } else {
                --bookmarkIndex;
                Q_ASSERT( bookmarkIndex >= 0 && bookmarkIndex < m_bookmarks.size() );
                return bookmarkData( bookmarkIndex, role );
            }
        }
    }

    return QVariant();
}

void TargetModel::setShowRoutingItems( bool show )
{
    m_showRoutingItems = show;
    reset();
}

void GoToDialogPrivate::createProgressAnimation()
{
    bool const smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen;
    int const iconSize = smallScreen ? 32 : 16;

    // Size parameters
    qreal const h = iconSize / 2.0; // Half of the icon size
    qreal const q = h / 2.0; // Quarter of the icon size
    qreal const d = 7.5; // Circle diameter
    qreal const r = d / 2.0; // Circle radius

    // Canvas parameters
    QImage canvas( iconSize, iconSize, QImage::Format_ARGB32 );
    QPainter painter( &canvas );
    painter.setRenderHint( QPainter::Antialiasing, true );
    painter.setPen( QColor ( Qt::gray ) );
    painter.setBrush( QColor( Qt::white ) );

    // Create all frames
    for( double t = 0.0; t < 2 * M_PI; t += M_PI / 8.0 ) {
        canvas.fill( Qt::transparent );
        QRectF firstCircle( h - r + q * cos( t ), h - r + q * sin( t ), d, d );
        QRectF secondCircle( h - r + q * cos( t + M_PI ), h - r + q * sin( t + M_PI ), d, d );
        painter.drawEllipse( firstCircle );
        painter.drawEllipse( secondCircle );
        m_progressAnimation.push_back( QIcon( QPixmap::fromImage( canvas ) ) );
    }
}

GoToDialogPrivate::GoToDialogPrivate( GoToDialog* parent, MarbleWidget* marbleWidget ) :
    m_parent( parent), m_marbleWidget( marbleWidget ), m_targetModel( marbleWidget ),
    m_runnerManager( 0 ), m_searchResult( new GeoDataDocument ), m_currentFrame( 0 )
{
    m_progressTimer.setInterval( 100 );
}

void GoToDialogPrivate::saveSelection( const QModelIndex &index )
{
    if ( m_parent->searchButton->isChecked() && m_searchResult->size() ) {
        QVariant coordinates = m_searchResultModel.data( index, MarblePlacemarkModel::CoordinateRole );
        m_lookAt = GeoDataLookAt();
        m_lookAt.setCoordinates( qVariantValue<GeoDataCoordinates>( coordinates ) );
        // By happy coincidence this equals OpenStreetMap tile level 16
        m_lookAt.setRange( 425.903 );
    } else {
        QVariant data = index.data( GeoDataLookAtRole );
        m_lookAt = qVariantValue<GeoDataLookAt>( data );
    }
    m_parent->accept();
}

void GoToDialog::startSearch()
{
    QString const searchTerm = searchLineEdit->text().trimmed();
    if ( searchTerm.isEmpty() ) {
        return;
    }

    if ( !d->m_runnerManager ) {
        d->m_runnerManager = new MarbleRunnerManager( d->m_marbleWidget->model()->pluginManager(), this );
        d->m_runnerManager->setModel( d->m_marbleWidget->model() );
        connect( d->m_runnerManager, SIGNAL( searchResultChanged( QVector<GeoDataPlacemark*> ) ),
                 this, SLOT( updateSearchResult( QVector<GeoDataPlacemark*> ) ) );
        connect( d->m_runnerManager, SIGNAL( searchFinished( QString ) ),
                this, SLOT( stopProgressAnimation() ) );
    }

    d->m_runnerManager->findPlacemarks( searchTerm );
    if ( d->m_progressAnimation.isEmpty() ) {
        d->createProgressAnimation();
    }
    d->m_progressTimer.start();
    progressButton->setVisible( true );
    searchLineEdit->setEnabled( false );
    updateResultMessage( 0 );
}

void GoToDialog::updateSearchResult( QVector<GeoDataPlacemark*> placemarks )
{
    d->m_searchResultModel.setRootDocument( 0 );
    d->m_searchResult->clear();
    foreach (GeoDataPlacemark *placemark, placemarks) {
        d->m_searchResult->append( new GeoDataPlacemark( *placemark ) );
    }
    d->m_searchResultModel.setRootDocument( d->m_searchResult );
    bookmarkListView->setModel( &d->m_searchResultModel );
    updateResultMessage( d->m_searchResultModel.rowCount() );
}

GoToDialog::GoToDialog( MarbleWidget* marbleWidget, QWidget * parent, Qt::WindowFlags flags ) :
    QDialog( parent, flags ), d( new GoToDialogPrivate( this, marbleWidget ) )
{
#ifdef Q_WS_MAEMO_5
        setAttribute( Qt::WA_Maemo5StackedWindow );
        setWindowFlags( Qt::Window );
#endif // Q_WS_MAEMO_5
    setupUi( this );

#if QT_VERSION >= 0x40700
    searchLineEdit->setPlaceholderText( tr( "Address or search term" ) );
#endif

    d->m_searchResultModel.setRootDocument( d->m_searchResult );
    bookmarkListView->setModel( &d->m_targetModel );
    connect( bookmarkListView, SIGNAL( activated( QModelIndex ) ),
             this, SLOT( saveSelection ( QModelIndex ) ) );
    connect( searchLineEdit, SIGNAL( returnPressed() ),
             this, SLOT( startSearch() ) );
    buttonBox->button( QDialogButtonBox::Close )->setAutoDefault( false );
    connect( searchButton, SIGNAL( clicked( bool ) ),
             this, SLOT( updateSearchMode() ) );
    connect( browseButton, SIGNAL( clicked( bool ) ),
             this, SLOT( updateSearchMode() ) );
    connect( &d->m_progressTimer, SIGNAL( timeout() ),
             this, SLOT( updateProgress() ) );
    connect( progressButton, SIGNAL( clicked( bool ) ),
             this, SLOT( stopProgressAnimation() ) );
    updateSearchMode();
    progressButton->setVisible( false );
}

GoToDialog::~GoToDialog()
{
    delete d;
}

GeoDataLookAt GoToDialog::lookAt() const
{
    return d->m_lookAt;
}

void GoToDialog::setShowRoutingItems( bool show )
{
    d->m_targetModel.setShowRoutingItems( show );
}

void GoToDialog::setSearchEnabled( bool enabled )
{
    browseButton->setVisible( enabled );
    searchButton->setVisible( enabled );
    if ( !enabled ) {
        searchButton->setChecked( false );
        updateSearchMode();
    }
}

void GoToDialog::updateSearchMode()
{
    bool const searchEnabled = searchButton->isChecked();
    searchLineEdit->setVisible( searchEnabled );
    descriptionLabel->setVisible( searchEnabled );
    progressButton->setVisible( searchEnabled && d->m_progressTimer.isActive() );
    if ( searchEnabled ) {
        bookmarkListView->setModel( &d->m_searchResultModel );
    } else {
        bookmarkListView->setModel( &d->m_targetModel );
    }
}

void GoToDialog::updateProgress()
{
    if ( !d->m_progressAnimation.isEmpty() ) {
        d->m_currentFrame = ( d->m_currentFrame + 1 ) % d->m_progressAnimation.size();
        QIcon frame = d->m_progressAnimation[d->m_currentFrame];
        progressButton->setIcon( frame );
    }
}

void GoToDialog::stopProgressAnimation()
{
    searchLineEdit->setEnabled( true );
    d->m_progressTimer.stop();
    updateResultMessage( bookmarkListView->model()->rowCount() );
    progressButton->setVisible( false );
}

void GoToDialog::updateResultMessage( int results )
{
    descriptionLabel->setText( tr( "%n results found.", "Number of search results", results ) );
}

}

#include "GoToDialog.moc"
<|endoftext|>


<commit_before>/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
 * This file is part of the libetonyek project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "IWORKTable.h"

#include <boost/numeric/conversion/cast.hpp>

#include <glm/glm.hpp>

#include "libetonyek_utils.h"
#include "IWORKDocumentInterface.h"
#include "IWORKProperties.h"
#include "IWORKStyle.h"
#include "IWORKTypes.h"

using boost::numeric_cast;

namespace libetonyek
{

namespace
{

void writeBorder(librevenge::RVNGPropertyList &props, const char *name, IWORKGridLine_t &line, std::size_t index)
{
  if (!line.is_tree_valid())
    line.build_tree();

  IWORKStylePtr_t style;
  line.search_tree(index, style);
  if (style && style->has<property::SFTStrokeProperty>())
  {
    const IWORKStroke &stroke = style->get<property::SFTStrokeProperty>();
    props.insert(name, makeBorder(stroke));
  }
}

void writeCellFormat(librevenge::RVNGPropertyList &props, const IWORKStylePtr_t &style, const IWORKCellType type, const boost::optional<std::string> &value)
{
  using namespace property;

  switch (type)
  {
  case IWORK_CELL_TYPE_NUMBER :
    if (style->has<SFTCellStylePropertyNumberFormat>() && value)
    {
      const IWORKNumberFormat &format = style->get<SFTCellStylePropertyNumberFormat>();

      if (style->getIdent())
        props.insert("librevenge:name", style->getIdent());

      props.insert("librevenge:value", get(value).c_str());

      switch (format.m_type)
      {
      case IWORK_CELL_NUMBER_TYPE_CURRENCY :
        props.insert("librevenge:value-type", "currency");
        props.insert("number:currency-style", format.m_currencyCode.c_str());
        break;
      case IWORK_CELL_NUMBER_TYPE_PERCENTAGE :
        props.insert("librevenge:value-type", "percentage");
        props.insert("number:decimal-places", format.m_decimalPlaces);
        break;
      case IWORK_CELL_NUMBER_TYPE_SCIENTIFIC :
        props.insert("librevenge:value-type", "scientific");
        props.insert("number:decimal-places", format.m_decimalPlaces);
        break;
      case IWORK_CELL_NUMBER_TYPE_DOUBLE :
        props.insert("librevenge:value-type", "double");
        props.insert("number:decimal-places", format.m_decimalPlaces);
        break;
      }
    }
    break;
  case IWORK_CELL_TYPE_DATE_TIME :
    if (style->has<SFTCellStylePropertyDateTimeFormat>() && value)
    {
      if (style->getIdent())
        props.insert("librevenge:name", style->getIdent());
      props.insert("librevenge:value-type", "date");
    }
    break;
  case IWORK_CELL_TYPE_DURATION :
    if (style->has<SFTCellStylePropertyDurationFormat>() && value)
    {
      // TODO: How to insert format and convert?
      // const IWORKDurationFormat &format = style->get<SFTCellStylePropertyDurationFormat>();
      // props.insert("librevenge:format",format.m_format.c_str());

      props.insert("librevenge:value", get(value).c_str());
      props.insert("librevenge:value-type", "time");

      if (style->getIdent())
        props.insert("librevenge:name", style->getIdent());
    }
    break;
  case IWORK_CELL_TYPE_BOOL:
    if (value)
    {
      props.insert("librevenge:value", get(value).c_str());
      props.insert("librevenge:value-type", "bool");
    }
    break;
  case IWORK_CELL_TYPE_TEXT :
  default:
    //TODO: librevenge:name ?
    if (value)
      props.insert("librevenge:value-type", "string");
    break;
  }
}

}

IWORKTable::Cell::Cell()
  : m_content()
  , m_columnSpan(1)
  , m_rowSpan(1)
  , m_covered(false)
  , m_formula()
  , m_style()
  , m_type(IWORK_CELL_TYPE_TEXT)
  , m_value()
{
}

IWORKTable::IWORKTable()
  : m_table()
  , m_columnSizes()
  , m_rowSizes()
  , m_verticalLines()
  , m_horizontalLines()
  , m_tableNameMap()
{
}

void IWORKTable::setSizes(const IWORKColumnSizes_t &columnSizes, const IWORKRowSizes_t &rowSizes)
{
  m_columnSizes = columnSizes;
  m_rowSizes = rowSizes;

  // init. content table of appropriate dimensions
  m_table = Table_t(m_rowSizes.size(), Row_t(m_columnSizes.size()));
}

void IWORKTable::setBorders(const IWORKGridLineList_t &verticalLines, const IWORKGridLineList_t &horizontalLines)
{
  m_verticalLines = verticalLines;
  m_horizontalLines = horizontalLines;
}

void IWORKTable::setTableNameMap(const IWORKTableNameMapPtr_t &tableNameMap)
{
  m_tableNameMap = tableNameMap;
}

void IWORKTable::insertCell(const unsigned column, const unsigned row, const boost::optional<std::string> &value, const IWORKOutputElements &content, const unsigned columnSpan, const unsigned rowSpan, const boost::optional<IWORKFormula> &formula, const IWORKStylePtr_t &style, const IWORKCellType type)
{
  if ((m_rowSizes.size() <= row) || (m_columnSizes.size() <= column))
    return;

  Cell cell;
  cell.m_content = content;
  cell.m_columnSpan = columnSpan;
  cell.m_rowSpan = rowSpan;
  cell.m_formula = formula;
  cell.m_style = style;
  cell.m_type = type;
  cell.m_value = value;
  m_table[row][column] = cell;
}

void IWORKTable::insertCoveredCell(const unsigned column, const unsigned row)
{
  if ((m_rowSizes.size() <= row) || (m_columnSizes.size() <= column))
    return;

  Cell cell;
  cell.m_covered = true;
  m_table[row][column] = cell;
}

void IWORKTable::draw(const librevenge::RVNGPropertyList &tableProps, IWORKOutputElements &elements)
{
  librevenge::RVNGPropertyListVector columnSizes;

  for (IWORKColumnSizes_t::const_iterator it = m_columnSizes.begin(); m_columnSizes.end() != it; ++it)
  {
    librevenge::RVNGPropertyList column;
    column.insert("style:column-width", pt2in(*it));
    columnSizes.append(column);
  }

  librevenge::RVNGPropertyList allTableProps(tableProps);
  allTableProps.insert("librevenge:table-columns", columnSizes);

  elements.addOpenTable(allTableProps);
  for (std::size_t r = 0; m_table.size() != r; ++r)
  {
    const Row_t &row = m_table[r];

    librevenge::RVNGPropertyList rowProps;
    rowProps.insert("style:row-height", pt2in(m_rowSizes[r]));

    elements.addOpenTableRow(rowProps);
    for (std::size_t c = 0; row.size() != c; ++c)
    {
      const Cell &cell = row[c];

      librevenge::RVNGPropertyList cellProps;
      cellProps.insert("librevenge:column", numeric_cast<int>(c));
      cellProps.insert("librevenge:row", numeric_cast<int>(r));
      cellProps.insert("fo:vertical-align", "middle");

      if (r < m_horizontalLines.size())
        writeBorder(cellProps, "fo:border-top", m_horizontalLines[r], c);
      if (r+1 < m_horizontalLines.size())
        writeBorder(cellProps, "fo:border-bottom", m_horizontalLines[r+1], c);
      if (c < m_verticalLines.size())
        writeBorder(cellProps, "fo:border-left", m_verticalLines[c], r);
      if (c+1 < m_verticalLines.size())
        writeBorder(cellProps, "fo:border-right", m_verticalLines[c+1], r);

      if (cell.m_covered)
      {
        elements.addInsertCoveredTableCell(cellProps);
      }
      else
      {
        if (1 < cell.m_columnSpan)
          cellProps.insert("table:number-columns-spanned", numeric_cast<int>(cell.m_columnSpan));
        if (1 < cell.m_rowSpan)
          cellProps.insert("table:number-rows-spanned", numeric_cast<int>(cell.m_rowSpan));

        if (cell.m_style)
          writeCellFormat(cellProps, cell.m_style, cell.m_type, cell.m_value);

        if (cell.m_formula)
          elements.addOpenFormulaCell(cellProps, cell.m_formula, m_tableNameMap);
        else
          elements.addOpenTableCell(cellProps);

        if (!cell.m_content.empty())
          elements.append(cell.m_content);

        elements.addCloseTableCell();
      }
    }
    elements.addCloseTableRow();
  }
  elements.addCloseTable();
}

}

/* vim:set shiftwidth=2 softtabstop=2 expandtab: */
<commit_msg>output duration as standard time format<commit_after>/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
 * This file is part of the libetonyek project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include "IWORKTable.h"

#include <boost/numeric/conversion/cast.hpp>

#include <glm/glm.hpp>

#include "libetonyek_xml.h"
#include "libetonyek_utils.h"
#include "IWORKDocumentInterface.h"
#include "IWORKProperties.h"
#include "IWORKStyle.h"
#include "IWORKTypes.h"

using boost::numeric_cast;

namespace libetonyek
{

namespace
{

void writeBorder(librevenge::RVNGPropertyList &props, const char *name, IWORKGridLine_t &line, std::size_t index)
{
  if (!line.is_tree_valid())
    line.build_tree();

  IWORKStylePtr_t style;
  line.search_tree(index, style);
  if (style && style->has<property::SFTStrokeProperty>())
  {
    const IWORKStroke &stroke = style->get<property::SFTStrokeProperty>();
    props.insert(name, makeBorder(stroke));
  }
}

void writeCellFormat(librevenge::RVNGPropertyList &props, const IWORKStylePtr_t &style, const IWORKCellType type, const boost::optional<std::string> &value)
{
  using namespace property;

  switch (type)
  {
  case IWORK_CELL_TYPE_NUMBER :
    if (style->has<SFTCellStylePropertyNumberFormat>() && value)
    {
      const IWORKNumberFormat &format = style->get<SFTCellStylePropertyNumberFormat>();

      if (style->getIdent())
        props.insert("librevenge:name", style->getIdent());

      props.insert("librevenge:value", get(value).c_str());

      switch (format.m_type)
      {
      case IWORK_CELL_NUMBER_TYPE_CURRENCY :
        props.insert("librevenge:value-type", "currency");
        props.insert("number:currency-style", format.m_currencyCode.c_str());
        break;
      case IWORK_CELL_NUMBER_TYPE_PERCENTAGE :
        props.insert("librevenge:value-type", "percentage");
        props.insert("number:decimal-places", format.m_decimalPlaces);
        break;
      case IWORK_CELL_NUMBER_TYPE_SCIENTIFIC :
        props.insert("librevenge:value-type", "scientific");
        props.insert("number:decimal-places", format.m_decimalPlaces);
        break;
      case IWORK_CELL_NUMBER_TYPE_DOUBLE :
        props.insert("librevenge:value-type", "double");
        props.insert("number:decimal-places", format.m_decimalPlaces);
        break;
      }
    }
    break;
  case IWORK_CELL_TYPE_DATE_TIME :
    if (style->has<SFTCellStylePropertyDateTimeFormat>() && value)
    {
      if (style->getIdent())
        props.insert("librevenge:name", style->getIdent());
      props.insert("librevenge:value-type", "date");
    }
    break;
  case IWORK_CELL_TYPE_DURATION :
    if (style->has<SFTCellStylePropertyDurationFormat>() && value)
    {
      // TODO: How to insert format and convert?
      // const IWORKDurationFormat &format = style->get<SFTCellStylePropertyDurationFormat>();
      // props.insert("librevenge:format",format.m_format.c_str());

      const int seconds = int_cast(get(value).c_str());
      props.insert("librevenge:value-type", "time");
      props.insert("librevenge:hours", seconds / 3600);
      props.insert("librevenge:minutes", (seconds % 3600) / 60);
      props.insert("librevenge:seconds", (seconds % 3600) % 60);

      if (style->getIdent())
        props.insert("librevenge:name", style->getIdent());
    }
    break;
  case IWORK_CELL_TYPE_BOOL:
    if (value)
    {
      props.insert("librevenge:value", get(value).c_str());
      props.insert("librevenge:value-type", "bool");
    }
    break;
  case IWORK_CELL_TYPE_TEXT :
  default:
    //TODO: librevenge:name ?
    if (value)
      props.insert("librevenge:value-type", "string");
    break;
  }
}

}

IWORKTable::Cell::Cell()
  : m_content()
  , m_columnSpan(1)
  , m_rowSpan(1)
  , m_covered(false)
  , m_formula()
  , m_style()
  , m_type(IWORK_CELL_TYPE_TEXT)
  , m_value()
{
}

IWORKTable::IWORKTable()
  : m_table()
  , m_columnSizes()
  , m_rowSizes()
  , m_verticalLines()
  , m_horizontalLines()
  , m_tableNameMap()
{
}

void IWORKTable::setSizes(const IWORKColumnSizes_t &columnSizes, const IWORKRowSizes_t &rowSizes)
{
  m_columnSizes = columnSizes;
  m_rowSizes = rowSizes;

  // init. content table of appropriate dimensions
  m_table = Table_t(m_rowSizes.size(), Row_t(m_columnSizes.size()));
}

void IWORKTable::setBorders(const IWORKGridLineList_t &verticalLines, const IWORKGridLineList_t &horizontalLines)
{
  m_verticalLines = verticalLines;
  m_horizontalLines = horizontalLines;
}

void IWORKTable::setTableNameMap(const IWORKTableNameMapPtr_t &tableNameMap)
{
  m_tableNameMap = tableNameMap;
}

void IWORKTable::insertCell(const unsigned column, const unsigned row, const boost::optional<std::string> &value, const IWORKOutputElements &content, const unsigned columnSpan, const unsigned rowSpan, const boost::optional<IWORKFormula> &formula, const IWORKStylePtr_t &style, const IWORKCellType type)
{
  if ((m_rowSizes.size() <= row) || (m_columnSizes.size() <= column))
    return;

  Cell cell;
  cell.m_content = content;
  cell.m_columnSpan = columnSpan;
  cell.m_rowSpan = rowSpan;
  cell.m_formula = formula;
  cell.m_style = style;
  cell.m_type = type;
  cell.m_value = value;
  m_table[row][column] = cell;
}

void IWORKTable::insertCoveredCell(const unsigned column, const unsigned row)
{
  if ((m_rowSizes.size() <= row) || (m_columnSizes.size() <= column))
    return;

  Cell cell;
  cell.m_covered = true;
  m_table[row][column] = cell;
}

void IWORKTable::draw(const librevenge::RVNGPropertyList &tableProps, IWORKOutputElements &elements)
{
  librevenge::RVNGPropertyListVector columnSizes;

  for (IWORKColumnSizes_t::const_iterator it = m_columnSizes.begin(); m_columnSizes.end() != it; ++it)
  {
    librevenge::RVNGPropertyList column;
    column.insert("style:column-width", pt2in(*it));
    columnSizes.append(column);
  }

  librevenge::RVNGPropertyList allTableProps(tableProps);
  allTableProps.insert("librevenge:table-columns", columnSizes);

  elements.addOpenTable(allTableProps);
  for (std::size_t r = 0; m_table.size() != r; ++r)
  {
    const Row_t &row = m_table[r];

    librevenge::RVNGPropertyList rowProps;
    rowProps.insert("style:row-height", pt2in(m_rowSizes[r]));

    elements.addOpenTableRow(rowProps);
    for (std::size_t c = 0; row.size() != c; ++c)
    {
      const Cell &cell = row[c];

      librevenge::RVNGPropertyList cellProps;
      cellProps.insert("librevenge:column", numeric_cast<int>(c));
      cellProps.insert("librevenge:row", numeric_cast<int>(r));
      cellProps.insert("fo:vertical-align", "middle");

      if (r < m_horizontalLines.size())
        writeBorder(cellProps, "fo:border-top", m_horizontalLines[r], c);
      if (r+1 < m_horizontalLines.size())
        writeBorder(cellProps, "fo:border-bottom", m_horizontalLines[r+1], c);
      if (c < m_verticalLines.size())
        writeBorder(cellProps, "fo:border-left", m_verticalLines[c], r);
      if (c+1 < m_verticalLines.size())
        writeBorder(cellProps, "fo:border-right", m_verticalLines[c+1], r);

      if (cell.m_covered)
      {
        elements.addInsertCoveredTableCell(cellProps);
      }
      else
      {
        if (1 < cell.m_columnSpan)
          cellProps.insert("table:number-columns-spanned", numeric_cast<int>(cell.m_columnSpan));
        if (1 < cell.m_rowSpan)
          cellProps.insert("table:number-rows-spanned", numeric_cast<int>(cell.m_rowSpan));

        if (cell.m_style)
          writeCellFormat(cellProps, cell.m_style, cell.m_type, cell.m_value);

        if (cell.m_formula)
          elements.addOpenFormulaCell(cellProps, cell.m_formula, m_tableNameMap);
        else
          elements.addOpenTableCell(cellProps);

        if (!cell.m_content.empty())
          elements.append(cell.m_content);

        elements.addCloseTableCell();
      }
    }
    elements.addCloseTableRow();
  }
  elements.addCloseTable();
}

}

/* vim:set shiftwidth=2 softtabstop=2 expandtab: */
<|endoftext|>


<commit_before>// Copyright 2019 DeepMind Technologies Limited.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "reverb/cc/client.h"

#include <algorithm>
#include <memory>

#include "grpcpp/support/channel_arguments.h"
#include "absl/strings/string_view.h"
#include "absl/time/time.h"
#include "reverb/cc/platform/grpc_utils.h"
#include "reverb/cc/platform/logging.h"
#include "reverb/cc/reverb_service.pb.h"
#include "reverb/cc/schema.pb.h"
#include "reverb/cc/support/grpc_util.h"
#include "reverb/cc/support/uint128.h"
#include "tensorflow/core/lib/core/status.h"
#include "tensorflow/core/platform/errors.h"

namespace deepmind {
namespace reverb {
namespace {

constexpr int kMaxMessageSize = 30 * 1000 * 1000;

grpc::ChannelArguments CreateChannelArguments() {
  grpc::ChannelArguments arguments;
  arguments.SetMaxReceiveMessageSize(kMaxMessageSize);
  arguments.SetMaxSendMessageSize(kMaxMessageSize);
  arguments.SetInt(GRPC_ARG_MAX_RECONNECT_BACKOFF_MS, 30 * 1000);
  arguments.SetLoadBalancingPolicyName("round_robin");
  return arguments;
}

}  // namespace

Client::Client(std::shared_ptr</* grpc_gen:: */ReverbService::StubInterface> stub)
    : stub_(std::move(stub)) {
  REVERB_CHECK(stub_ != nullptr);
}

Client::Client(absl::string_view server_address)
    : stub_(/* grpc_gen:: */ReverbService::NewStub(
          CreateCustomGrpcChannel(server_address, MakeChannelCredentials(),
                                  CreateChannelArguments()))) {}

tensorflow::Status Client::MaybeUpdateServerInfoCache(
    absl::Duration timeout,
    std::shared_ptr<internal::FlatSignatureMap>* cached_flat_signatures) {
  // TODO(b/154927570): Once tables can be mutated on the server, we'll need to
  // decide a new rule for updating the server info, instead of doing it just
  // once at the beginning.
  {
    // Exit early if we have table info cached.
    absl::ReaderMutexLock lock(&cached_table_mu_);
    if (cached_flat_signatures_) {
      *cached_flat_signatures = cached_flat_signatures_;
      return tensorflow::Status::OK();
    }
  }

  if (timeout == -absl::InfiniteDuration()) {
    // If timeout is -infinity, the user asked for data to be returned
    // immediately and without error; but we don't have anything already cached.
    // Just act like everything is fine! (Return empty signatures).
    *cached_flat_signatures = std::make_shared<internal::FlatSignatureMap>();
    return tensorflow::Status::OK();
  }

  // This performs an RPC, so don't run it within a mutex.
  // Note, this operation can run into a race condition where multiple
  // threads of the same Client request server info, get different
  // values, and one of these overwrites cached_table_info_ with a staler
  // ServerInfo after another thread writes a newer version of ServerInfo
  // Then future writers see stale signatures.
  //
  // In practice this isn't a real issue because:
  //  (1) This type of concurrency is not common: once ServerInfo
  //      is set, this code path isn't executed again.
  //  (2) ServerInfo doesn't change often on a reverb server.
  //  (3) Due to the default gRPC client load balancing mechanism,
  //      a client with a stub connection to one IP of a group of
  //      servers will always use the same IP address for all
  //      consecutive requests.  So even concurrent requests will all
  //      go to the same server ("pick_first" policy):
  //
  //      https://github.com/grpc/grpc/blob/631fe79f84af295c60aea5693350b45154827398/src/core/ext/filters/client_channel/client_channel.cc#L1661
  struct ServerInfo info;
  TF_RETURN_IF_ERROR(GetServerInfo(timeout, &info));

  absl::MutexLock lock(&cached_table_mu_);
  TF_RETURN_IF_ERROR(LockedUpdateServerInfoCache(info));
  *cached_flat_signatures = cached_flat_signatures_;
  return tensorflow::Status::OK();
}

tensorflow::Status Client::NewWriter(int chunk_length, int max_timesteps,
                                     bool delta_encoded,
                                     std::unique_ptr<Writer>* writer) {
  // TODO(b/154928265): caching this request?  For example, if
  // it's been N seconds or minutes, it may be time to
  // get an updated ServerInfo and see if there are new tables.
  std::shared_ptr<internal::FlatSignatureMap> cached_flat_signatures;
  // TODO(b/154927687): It is not ideal that this blocks forever. We should
  // probably limit this and ignore the signature if it couldn't be found within
  // some limits.
  TF_RETURN_IF_ERROR(MaybeUpdateServerInfoCache(absl::InfiniteDuration(),
                                                &cached_flat_signatures));
  *writer = absl::make_unique<Writer>(stub_, chunk_length, max_timesteps,
                                      delta_encoded,
                                      std::move(cached_flat_signatures));
  return tensorflow::Status::OK();
}

tensorflow::Status Client::MutatePriorities(
    absl::string_view table, const std::vector<KeyWithPriority>& updates,
    const std::vector<uint64_t>& deletes) {
  grpc::ClientContext context;
  context.set_wait_for_ready(true);
  MutatePrioritiesRequest request;
  request.set_table(table.data(), table.size());
  for (const KeyWithPriority& item : updates) {
    *request.add_updates() = item;
  }
  for (int64_t key : deletes) {
    request.add_delete_keys(key);
  }
  MutatePrioritiesResponse response;
  return FromGrpcStatus(stub_->MutatePriorities(&context, request, &response));
}

tensorflow::Status Client::NewSampler(
    const std::string& table, const Sampler::Options& options,
    internal::DtypesAndShapes dtypes_and_shapes,
    std::unique_ptr<Sampler>* sampler) {
  *sampler = absl::make_unique<Sampler>(stub_, table, options,
                                        std::move(dtypes_and_shapes));
  return tensorflow::Status::OK();
}

tensorflow::Status Client::NewSampler(const std::string& table,
                                      const Sampler::Options& options,
                                      absl::Duration validation_timeout,
                                      std::unique_ptr<Sampler>* sampler) {
  internal::DtypesAndShapes dtypes_and_shapes;
  auto status = GetDtypesAndShapesForSampler(table, validation_timeout,
                                             &dtypes_and_shapes);

  if (tensorflow::errors::IsDeadlineExceeded(status)) {
    REVERB_LOG(REVERB_WARNING)
        << "Unable to validate shapes and dtypes of new sampler for '" << table
        << "' as server could not be reached in time (" << validation_timeout
        << "). We were thus unable to fetch signature from server. The "
           "sampler will be constructed without validating the dtypes "
           "and shapes.";
  }

  return NewSampler(table, options, std::move(dtypes_and_shapes), sampler);
}

tensorflow::Status Client::GetDtypesAndShapesForSampler(
    const std::string& table, absl::Duration validation_timeout,
    internal::DtypesAndShapes* dtypes_and_shapes) {
  // TODO(b/154928265): caching this request?  For example, if
  // it's been N seconds or minutes, it may be time to
  // get an updated ServerInfo and see if there are new tables.

  std::shared_ptr<internal::FlatSignatureMap> cached_flat_signatures;
  TF_RETURN_IF_ERROR(
      MaybeUpdateServerInfoCache(validation_timeout, &cached_flat_signatures));

  const auto iter = cached_flat_signatures->find(table);
  if (iter == cached_flat_signatures->end()) {
    std::vector<std::string> table_names;
    for (const auto& table : *cached_flat_signatures) {
      table_names.push_back(absl::StrCat("'", table.first, "'"));
    }
    REVERB_LOG(REVERB_WARNING)
        << "Unable to find table '" << table
        << "' in server signature.  Perhaps the table hasn't yet been added to "
           "the server?  Available tables: ["
        << absl::StrJoin(table_names, ", ") << "].";
    dtypes_and_shapes->reset();
  } else if (!iter->second) {
    // Found the table, but signature is empty.
    dtypes_and_shapes->reset();
  } else {
    // Found the table, and found a signature.  Add the 4-element prefix for the
    // SampleInfo.
    const auto& old_dtypes_and_shapes = iter->second;
    std::vector<internal::TensorSpec> dtypes_and_shapes_vec;
    dtypes_and_shapes_vec.reserve(4 + old_dtypes_and_shapes->size());
    // First element of sampled signature is the key.
    dtypes_and_shapes_vec.push_back(
        {"key", tensorflow::DT_UINT64, tensorflow::PartialTensorShape({})});
    // Second element of sampled signature is the probability value.
    dtypes_and_shapes_vec.push_back({"probability", tensorflow::DT_DOUBLE,
                                     tensorflow::PartialTensorShape({})});
    // Third element of sampled signature is the size of the table.
    dtypes_and_shapes_vec.push_back({"table_size", tensorflow::DT_INT64,
                                     tensorflow::PartialTensorShape({})});
    // Fourth element of sampled signature is the priority value.
    dtypes_and_shapes_vec.push_back({"priority", tensorflow::DT_DOUBLE,
                                     tensorflow::PartialTensorShape({})});
    for (const auto& dtype_and_shape : *old_dtypes_and_shapes) {
      dtypes_and_shapes_vec.push_back(dtype_and_shape);
    }

    dtypes_and_shapes->emplace(std::move(dtypes_and_shapes_vec));
  }
  return tensorflow::Status::OK();
}

tensorflow::Status Client::NewSampler(
    const std::string& table, const Sampler::Options& options,
    const tensorflow::DataTypeVector& validation_dtypes,
    const std::vector<tensorflow::PartialTensorShape>& validation_shapes,
    absl::Duration validation_timeout, std::unique_ptr<Sampler>* sampler) {
  if (validation_dtypes.size() != validation_shapes.size()) {
    return tensorflow::errors::InvalidArgument(
        "validation_shapes.size() != validation_dtypes.size() (",
        validation_shapes.size(), " vs. ", validation_dtypes.size(), ")");
  }

  internal::DtypesAndShapes dtypes_and_shapes;
  TF_RETURN_IF_ERROR(GetDtypesAndShapesForSampler(table, validation_timeout,
                                                  &dtypes_and_shapes));
  // Only perform check if the table had a signature associated with it.
  if (dtypes_and_shapes) {
    if (dtypes_and_shapes->size() != validation_shapes.size()) {
      return tensorflow::errors::InvalidArgument(
          "Inconsistent number of tensors requested from table '", table,
          "'.  Requested ", validation_shapes.size(),
          " tensors, but table signature shows ", dtypes_and_shapes->size(),
          " tensors.  Table signature: ",
          internal::DtypesShapesString(*dtypes_and_shapes));
    }
    for (int i = 0; i < dtypes_and_shapes->size(); ++i) {
      if (dtypes_and_shapes->at(i).dtype != validation_dtypes[i] ||
          !dtypes_and_shapes->at(i).shape.IsCompatibleWith(
              validation_shapes[i])) {
        return tensorflow::errors::InvalidArgument(
            "Requested incompatible tensor at flattened index ", i,
            " from table '", table, "'.  Requested (dtype, shape): (",
            tensorflow::DataTypeString(validation_dtypes[i]), ", ",
            validation_shapes[i].DebugString(),
            ").  Signature (dtype, shape): (",
            tensorflow::DataTypeString(dtypes_and_shapes->at(i).dtype), ", ",
            dtypes_and_shapes->at(i).shape.DebugString(),
            ").  Table signature: ",
            internal::DtypesShapesString(*dtypes_and_shapes));
      }
    }
  } else {
    // dtypes_and_shapes lacks any signature info; build it from
    // the validation inputs.
    std::vector<internal::TensorSpec> dtypes_and_shapes_vec;
    dtypes_and_shapes_vec.reserve(validation_shapes.size());
    for (int i = 0; i < validation_shapes.size(); ++i) {
      dtypes_and_shapes_vec.push_back(
          {/*name=*/"?", validation_dtypes[i], validation_shapes[i]});
    }
    dtypes_and_shapes.emplace(std::move(dtypes_and_shapes_vec));
  }

  // TODO(b/154927849): Do sanity checks on the buffer_size and max_samples.
  // TODO(b/154928566): Maybe we don't even need to expose the buffer_size.
  return NewSampler(table, options, std::move(dtypes_and_shapes), sampler);
}

tensorflow::Status Client::GetServerInfo(absl::Duration timeout,
                                         struct ServerInfo* info) {
  grpc::ClientContext context;
  context.set_wait_for_ready(true);
  if (timeout != absl::InfiniteDuration()) {
    context.set_deadline(std::chrono::system_clock::now() +
                         absl::ToChronoSeconds(timeout));
  }

  ServerInfoRequest request;
  ServerInfoResponse response;
  TF_RETURN_IF_ERROR(
      FromGrpcStatus(stub_->ServerInfo(&context, request, &response)));
  info->tables_state_id = MessageToUint128(response.tables_state_id());
  for (class TableInfo& table : *response.mutable_table_info()) {
    info->table_info.emplace_back(std::move(table));
  }
  return tensorflow::Status::OK();
}

tensorflow::Status Client::ServerInfo(struct ServerInfo* info) {
  return ServerInfo(absl::InfiniteDuration(), info);
}

tensorflow::Status Client::ServerInfo(absl::Duration timeout,
                                      struct ServerInfo* info) {
  struct ServerInfo local_info;
  TF_RETURN_IF_ERROR(GetServerInfo(timeout, &local_info));
  {
    absl::MutexLock lock(&cached_table_mu_);
    TF_RETURN_IF_ERROR(LockedUpdateServerInfoCache(local_info));
  }
  std::swap(*info, local_info);
  return tensorflow::Status::OK();
}

tensorflow::Status Client::LockedUpdateServerInfoCache(
    const struct ServerInfo& info) {
  if (!cached_flat_signatures_ || tables_state_id_ != info.tables_state_id) {
    internal::FlatSignatureMap signatures;
    for (const auto& table_info : info.table_info) {
      TF_RETURN_IF_ERROR(internal::FlatSignatureFromTableInfo(
          table_info, &(signatures[table_info.name()])));
    }
    cached_flat_signatures_.reset(
        new internal::FlatSignatureMap(std::move(signatures)));
    tables_state_id_ = info.tables_state_id;
  }
  return tensorflow::Status::OK();
}

tensorflow::Status Client::Reset(const std::string& table) {
  grpc::ClientContext context;
  context.set_wait_for_ready(true);
  ResetRequest request;
  request.set_table(table);
  ResetResponse response;
  return FromGrpcStatus(stub_->Reset(&context, request, &response));
}

tensorflow::Status Client::Checkpoint(std::string* path) {
  grpc::ClientContext context;
  context.set_fail_fast(true);
  CheckpointRequest request;
  CheckpointResponse response;
  TF_RETURN_IF_ERROR(
      FromGrpcStatus(stub_->Checkpoint(&context, request, &response)));
  *path = response.checkpoint_path();
  return tensorflow::Status::OK();
}

}  // namespace reverb
}  // namespace deepmind
<commit_msg>Remove obsolete TODO.<commit_after>// Copyright 2019 DeepMind Technologies Limited.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "reverb/cc/client.h"

#include <algorithm>
#include <memory>

#include "grpcpp/support/channel_arguments.h"
#include "absl/strings/string_view.h"
#include "absl/time/time.h"
#include "reverb/cc/platform/grpc_utils.h"
#include "reverb/cc/platform/logging.h"
#include "reverb/cc/reverb_service.pb.h"
#include "reverb/cc/schema.pb.h"
#include "reverb/cc/support/grpc_util.h"
#include "reverb/cc/support/uint128.h"
#include "tensorflow/core/lib/core/status.h"
#include "tensorflow/core/platform/errors.h"

namespace deepmind {
namespace reverb {
namespace {

constexpr int kMaxMessageSize = 30 * 1000 * 1000;

grpc::ChannelArguments CreateChannelArguments() {
  grpc::ChannelArguments arguments;
  arguments.SetMaxReceiveMessageSize(kMaxMessageSize);
  arguments.SetMaxSendMessageSize(kMaxMessageSize);
  arguments.SetInt(GRPC_ARG_MAX_RECONNECT_BACKOFF_MS, 30 * 1000);
  arguments.SetLoadBalancingPolicyName("round_robin");
  return arguments;
}

}  // namespace

Client::Client(std::shared_ptr</* grpc_gen:: */ReverbService::StubInterface> stub)
    : stub_(std::move(stub)) {
  REVERB_CHECK(stub_ != nullptr);
}

Client::Client(absl::string_view server_address)
    : stub_(/* grpc_gen:: */ReverbService::NewStub(
          CreateCustomGrpcChannel(server_address, MakeChannelCredentials(),
                                  CreateChannelArguments()))) {}

tensorflow::Status Client::MaybeUpdateServerInfoCache(
    absl::Duration timeout,
    std::shared_ptr<internal::FlatSignatureMap>* cached_flat_signatures) {
  // TODO(b/154927570): Once tables can be mutated on the server, we'll need to
  // decide a new rule for updating the server info, instead of doing it just
  // once at the beginning.
  {
    // Exit early if we have table info cached.
    absl::ReaderMutexLock lock(&cached_table_mu_);
    if (cached_flat_signatures_) {
      *cached_flat_signatures = cached_flat_signatures_;
      return tensorflow::Status::OK();
    }
  }

  if (timeout == -absl::InfiniteDuration()) {
    // If timeout is -infinity, the user asked for data to be returned
    // immediately and without error; but we don't have anything already cached.
    // Just act like everything is fine! (Return empty signatures).
    *cached_flat_signatures = std::make_shared<internal::FlatSignatureMap>();
    return tensorflow::Status::OK();
  }

  // This performs an RPC, so don't run it within a mutex.
  // Note, this operation can run into a race condition where multiple
  // threads of the same Client request server info, get different
  // values, and one of these overwrites cached_table_info_ with a staler
  // ServerInfo after another thread writes a newer version of ServerInfo
  // Then future writers see stale signatures.
  //
  // In practice this isn't a real issue because:
  //  (1) This type of concurrency is not common: once ServerInfo
  //      is set, this code path isn't executed again.
  //  (2) ServerInfo doesn't change often on a reverb server.
  //  (3) Due to the default gRPC client load balancing mechanism,
  //      a client with a stub connection to one IP of a group of
  //      servers will always use the same IP address for all
  //      consecutive requests.  So even concurrent requests will all
  //      go to the same server ("pick_first" policy):
  //
  //      https://github.com/grpc/grpc/blob/631fe79f84af295c60aea5693350b45154827398/src/core/ext/filters/client_channel/client_channel.cc#L1661
  struct ServerInfo info;
  TF_RETURN_IF_ERROR(GetServerInfo(timeout, &info));

  absl::MutexLock lock(&cached_table_mu_);
  TF_RETURN_IF_ERROR(LockedUpdateServerInfoCache(info));
  *cached_flat_signatures = cached_flat_signatures_;
  return tensorflow::Status::OK();
}

tensorflow::Status Client::NewWriter(int chunk_length, int max_timesteps,
                                     bool delta_encoded,
                                     std::unique_ptr<Writer>* writer) {
  // TODO(b/154928265): caching this request?  For example, if
  // it's been N seconds or minutes, it may be time to
  // get an updated ServerInfo and see if there are new tables.
  std::shared_ptr<internal::FlatSignatureMap> cached_flat_signatures;
  // TODO(b/154927687): It is not ideal that this blocks forever. We should
  // probably limit this and ignore the signature if it couldn't be found within
  // some limits.
  TF_RETURN_IF_ERROR(MaybeUpdateServerInfoCache(absl::InfiniteDuration(),
                                                &cached_flat_signatures));
  *writer = absl::make_unique<Writer>(stub_, chunk_length, max_timesteps,
                                      delta_encoded,
                                      std::move(cached_flat_signatures));
  return tensorflow::Status::OK();
}

tensorflow::Status Client::MutatePriorities(
    absl::string_view table, const std::vector<KeyWithPriority>& updates,
    const std::vector<uint64_t>& deletes) {
  grpc::ClientContext context;
  context.set_wait_for_ready(true);
  MutatePrioritiesRequest request;
  request.set_table(table.data(), table.size());
  for (const KeyWithPriority& item : updates) {
    *request.add_updates() = item;
  }
  for (int64_t key : deletes) {
    request.add_delete_keys(key);
  }
  MutatePrioritiesResponse response;
  return FromGrpcStatus(stub_->MutatePriorities(&context, request, &response));
}

tensorflow::Status Client::NewSampler(
    const std::string& table, const Sampler::Options& options,
    internal::DtypesAndShapes dtypes_and_shapes,
    std::unique_ptr<Sampler>* sampler) {
  *sampler = absl::make_unique<Sampler>(stub_, table, options,
                                        std::move(dtypes_and_shapes));
  return tensorflow::Status::OK();
}

tensorflow::Status Client::NewSampler(const std::string& table,
                                      const Sampler::Options& options,
                                      absl::Duration validation_timeout,
                                      std::unique_ptr<Sampler>* sampler) {
  internal::DtypesAndShapes dtypes_and_shapes;
  auto status = GetDtypesAndShapesForSampler(table, validation_timeout,
                                             &dtypes_and_shapes);

  if (tensorflow::errors::IsDeadlineExceeded(status)) {
    REVERB_LOG(REVERB_WARNING)
        << "Unable to validate shapes and dtypes of new sampler for '" << table
        << "' as server could not be reached in time (" << validation_timeout
        << "). We were thus unable to fetch signature from server. The "
           "sampler will be constructed without validating the dtypes "
           "and shapes.";
  }

  return NewSampler(table, options, std::move(dtypes_and_shapes), sampler);
}

tensorflow::Status Client::GetDtypesAndShapesForSampler(
    const std::string& table, absl::Duration validation_timeout,
    internal::DtypesAndShapes* dtypes_and_shapes) {
  // TODO(b/154928265): caching this request?  For example, if
  // it's been N seconds or minutes, it may be time to
  // get an updated ServerInfo and see if there are new tables.

  std::shared_ptr<internal::FlatSignatureMap> cached_flat_signatures;
  TF_RETURN_IF_ERROR(
      MaybeUpdateServerInfoCache(validation_timeout, &cached_flat_signatures));

  const auto iter = cached_flat_signatures->find(table);
  if (iter == cached_flat_signatures->end()) {
    std::vector<std::string> table_names;
    for (const auto& table : *cached_flat_signatures) {
      table_names.push_back(absl::StrCat("'", table.first, "'"));
    }
    REVERB_LOG(REVERB_WARNING)
        << "Unable to find table '" << table
        << "' in server signature.  Perhaps the table hasn't yet been added to "
           "the server?  Available tables: ["
        << absl::StrJoin(table_names, ", ") << "].";
    dtypes_and_shapes->reset();
  } else if (!iter->second) {
    // Found the table, but signature is empty.
    dtypes_and_shapes->reset();
  } else {
    // Found the table, and found a signature.  Add the 4-element prefix for the
    // SampleInfo.
    const auto& old_dtypes_and_shapes = iter->second;
    std::vector<internal::TensorSpec> dtypes_and_shapes_vec;
    dtypes_and_shapes_vec.reserve(4 + old_dtypes_and_shapes->size());
    // First element of sampled signature is the key.
    dtypes_and_shapes_vec.push_back(
        {"key", tensorflow::DT_UINT64, tensorflow::PartialTensorShape({})});
    // Second element of sampled signature is the probability value.
    dtypes_and_shapes_vec.push_back({"probability", tensorflow::DT_DOUBLE,
                                     tensorflow::PartialTensorShape({})});
    // Third element of sampled signature is the size of the table.
    dtypes_and_shapes_vec.push_back({"table_size", tensorflow::DT_INT64,
                                     tensorflow::PartialTensorShape({})});
    // Fourth element of sampled signature is the priority value.
    dtypes_and_shapes_vec.push_back({"priority", tensorflow::DT_DOUBLE,
                                     tensorflow::PartialTensorShape({})});
    for (const auto& dtype_and_shape : *old_dtypes_and_shapes) {
      dtypes_and_shapes_vec.push_back(dtype_and_shape);
    }

    dtypes_and_shapes->emplace(std::move(dtypes_and_shapes_vec));
  }
  return tensorflow::Status::OK();
}

tensorflow::Status Client::NewSampler(
    const std::string& table, const Sampler::Options& options,
    const tensorflow::DataTypeVector& validation_dtypes,
    const std::vector<tensorflow::PartialTensorShape>& validation_shapes,
    absl::Duration validation_timeout, std::unique_ptr<Sampler>* sampler) {
  if (validation_dtypes.size() != validation_shapes.size()) {
    return tensorflow::errors::InvalidArgument(
        "validation_shapes.size() != validation_dtypes.size() (",
        validation_shapes.size(), " vs. ", validation_dtypes.size(), ")");
  }

  internal::DtypesAndShapes dtypes_and_shapes;
  TF_RETURN_IF_ERROR(GetDtypesAndShapesForSampler(table, validation_timeout,
                                                  &dtypes_and_shapes));
  // Only perform check if the table had a signature associated with it.
  if (dtypes_and_shapes) {
    if (dtypes_and_shapes->size() != validation_shapes.size()) {
      return tensorflow::errors::InvalidArgument(
          "Inconsistent number of tensors requested from table '", table,
          "'.  Requested ", validation_shapes.size(),
          " tensors, but table signature shows ", dtypes_and_shapes->size(),
          " tensors.  Table signature: ",
          internal::DtypesShapesString(*dtypes_and_shapes));
    }
    for (int i = 0; i < dtypes_and_shapes->size(); ++i) {
      if (dtypes_and_shapes->at(i).dtype != validation_dtypes[i] ||
          !dtypes_and_shapes->at(i).shape.IsCompatibleWith(
              validation_shapes[i])) {
        return tensorflow::errors::InvalidArgument(
            "Requested incompatible tensor at flattened index ", i,
            " from table '", table, "'.  Requested (dtype, shape): (",
            tensorflow::DataTypeString(validation_dtypes[i]), ", ",
            validation_shapes[i].DebugString(),
            ").  Signature (dtype, shape): (",
            tensorflow::DataTypeString(dtypes_and_shapes->at(i).dtype), ", ",
            dtypes_and_shapes->at(i).shape.DebugString(),
            ").  Table signature: ",
            internal::DtypesShapesString(*dtypes_and_shapes));
      }
    }
  } else {
    // dtypes_and_shapes lacks any signature info; build it from
    // the validation inputs.
    std::vector<internal::TensorSpec> dtypes_and_shapes_vec;
    dtypes_and_shapes_vec.reserve(validation_shapes.size());
    for (int i = 0; i < validation_shapes.size(); ++i) {
      dtypes_and_shapes_vec.push_back(
          {/*name=*/"?", validation_dtypes[i], validation_shapes[i]});
    }
    dtypes_and_shapes.emplace(std::move(dtypes_and_shapes_vec));
  }

  // TODO(b/154927849): Do sanity checks on the buffer_size and max_samples.
  return NewSampler(table, options, std::move(dtypes_and_shapes), sampler);
}

tensorflow::Status Client::GetServerInfo(absl::Duration timeout,
                                         struct ServerInfo* info) {
  grpc::ClientContext context;
  context.set_wait_for_ready(true);
  if (timeout != absl::InfiniteDuration()) {
    context.set_deadline(std::chrono::system_clock::now() +
                         absl::ToChronoSeconds(timeout));
  }

  ServerInfoRequest request;
  ServerInfoResponse response;
  TF_RETURN_IF_ERROR(
      FromGrpcStatus(stub_->ServerInfo(&context, request, &response)));
  info->tables_state_id = MessageToUint128(response.tables_state_id());
  for (class TableInfo& table : *response.mutable_table_info()) {
    info->table_info.emplace_back(std::move(table));
  }
  return tensorflow::Status::OK();
}

tensorflow::Status Client::ServerInfo(struct ServerInfo* info) {
  return ServerInfo(absl::InfiniteDuration(), info);
}

tensorflow::Status Client::ServerInfo(absl::Duration timeout,
                                      struct ServerInfo* info) {
  struct ServerInfo local_info;
  TF_RETURN_IF_ERROR(GetServerInfo(timeout, &local_info));
  {
    absl::MutexLock lock(&cached_table_mu_);
    TF_RETURN_IF_ERROR(LockedUpdateServerInfoCache(local_info));
  }
  std::swap(*info, local_info);
  return tensorflow::Status::OK();
}

tensorflow::Status Client::LockedUpdateServerInfoCache(
    const struct ServerInfo& info) {
  if (!cached_flat_signatures_ || tables_state_id_ != info.tables_state_id) {
    internal::FlatSignatureMap signatures;
    for (const auto& table_info : info.table_info) {
      TF_RETURN_IF_ERROR(internal::FlatSignatureFromTableInfo(
          table_info, &(signatures[table_info.name()])));
    }
    cached_flat_signatures_.reset(
        new internal::FlatSignatureMap(std::move(signatures)));
    tables_state_id_ = info.tables_state_id;
  }
  return tensorflow::Status::OK();
}

tensorflow::Status Client::Reset(const std::string& table) {
  grpc::ClientContext context;
  context.set_wait_for_ready(true);
  ResetRequest request;
  request.set_table(table);
  ResetResponse response;
  return FromGrpcStatus(stub_->Reset(&context, request, &response));
}

tensorflow::Status Client::Checkpoint(std::string* path) {
  grpc::ClientContext context;
  context.set_fail_fast(true);
  CheckpointRequest request;
  CheckpointResponse response;
  TF_RETURN_IF_ERROR(
      FromGrpcStatus(stub_->Checkpoint(&context, request, &response)));
  *path = response.checkpoint_path();
  return tensorflow::Status::OK();
}

}  // namespace reverb
}  // namespace deepmind
<|endoftext|>