You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdebase/kioslave/thumbnail/thumbnail.cpp

441 lines
13 KiB

/* This file is part of the KDE libraries
Copyright (C) 2000 Malte Starostik <malte@kde.org>
2000 Carsten Pfeiffer <pfeiffer@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include <stdlib.h>
#include <unistd.h>
#ifdef __FreeBSD__
#include <machine/param.h>
#endif
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <qfile.h>
#include <qbitmap.h>
#include <qpixmap.h>
#include <qpainter.h>
#include <qimage.h>
#include <qbuffer.h>
#include <kdatastream.h> // Do not remove, needed for correct bool serialization
#include <kurl.h>
#include <kapplication.h>
#include <kglobal.h>
#include <kiconloader.h>
#include <kimageeffect.h>
#include <kmimetype.h>
#include <klibloader.h>
#include <kdebug.h>
#include <kservice.h>
#include <kservicetype.h>
#include <kuserprofile.h>
#include <kfilemetainfo.h>
#include <klocale.h>
#include <config.h> // For HAVE_NICE
#include "thumbnail.h"
#include <kio/thumbcreator.h>
// Use correctly KInstance instead of KApplication (but then no QPixmap)
#undef USE_KINSTANCE
// Fix thumbnail: protocol
#define THUMBNAIL_HACK (1)
#ifdef THUMBNAIL_HACK
# include <qfileinfo.h>
# include <ktrader.h>
#endif
// Recognized metadata entries:
// mimeType - the mime type of the file, used for the overlay icon if any
// width - maximum width for the thumbnail
// height - maximum height for the thumbnail
// iconSize - the size of the overlay icon to use if any
// iconAlpha - the transparency value used for icon overlays
// plugin - the name of the plugin library to be used for thumbnail creation.
// Provided by the application to save an addition KTrader
// query here.
// shmid - the shared memory segment id to write the image's data to.
// The segment is assumed to provide enough space for a 32-bit
// image sized width x height pixels.
// If this is given, the data returned by the slave will be:
// int width
// int height
// int depth
// Otherwise, the data returned is the image in PNG format.
using namespace KIO;
extern "C"
{
KDE_EXPORT int kdemain(int argc, char **argv);
}
int kdemain(int argc, char **argv)
{
#ifdef HAVE_NICE
nice( 5 );
#endif
#ifdef USE_KINSTANCE
KInstance instance("kio_thumbnail");
#else
// creating KApplication in a slave in not a very good idea,
// as dispatchLoop() doesn't allow it to process its messages,
// so it for example wouldn't reply to ksmserver - on the other
// hand, this slave uses QPixmaps for some reason, and they
// need QApplication
// and HTML previews need even KApplication :(
putenv(strdup("SESSION_MANAGER="));
KApplication::disableAutoDcopRegistration();
KApplication app(argc, argv, "kio_thumbnail", false, true);
#endif
if (argc != 4)
{
kdError(7115) << "Usage: kio_thumbnail protocol domain-socket1 domain-socket2" << endl;
exit(-1);
}
ThumbnailProtocol slave(argv[2], argv[3]);
slave.dispatchLoop();
return 0;
}
ThumbnailProtocol::ThumbnailProtocol(const QCString &pool, const QCString &app)
: SlaveBase("thumbnail", pool, app)
{
m_creators.setAutoDelete(true);
m_iconDict.setAutoDelete(true);
m_iconSize = 0;
}
ThumbnailProtocol::~ThumbnailProtocol()
{
}
void ThumbnailProtocol::get(const KURL &url)
{
m_mimeType = metaData("mimeType");
kdDebug(7115) << "Wanting MIME Type:" << m_mimeType << endl;
#ifdef THUMBNAIL_HACK
// ### HACK
bool direct=false;
if (m_mimeType.isEmpty())
{
kdDebug(7115) << "PATH: " << url.path() << endl;
QFileInfo info(url.path());
if (info.isDir())
{
// We cannot process a directory
error(KIO::ERR_IS_DIRECTORY,url.path());
return;
}
else if (!info.exists())
{
// The file does not exist
error(KIO::ERR_DOES_NOT_EXIST,url.path());
return;
}
else if (!info.isReadable())
{
// The file is not readable!
error(KIO::ERR_COULD_NOT_READ,url.path());
return;
}
m_mimeType = KMimeType::findByURL(url)->name();
kdDebug(7115) << "Guessing MIME Type:" << m_mimeType << endl;
direct=true; // thumbnail: was probably called from Konqueror
}
#endif
if (m_mimeType.isEmpty())
{
error(KIO::ERR_INTERNAL, i18n("No MIME Type specified."));
return;
}
m_width = metaData("width").toInt();
m_height = metaData("height").toInt();
int iconSize = metaData("iconSize").toInt();
if (m_width < 0 || m_height < 0)
{
error(KIO::ERR_INTERNAL, i18n("No or invalid size specified."));
return;
}
#ifdef THUMBNAIL_HACK
else if (!m_width || !m_height)
{
kdDebug(7115) << "Guessing height, width, icon sizre!" << endl;
m_width=128;
m_height=128;
iconSize=128;
}
#endif
if (!iconSize)
iconSize = KGlobal::iconLoader()->currentSize(KIcon::Desktop);
if (iconSize != m_iconSize)
m_iconDict.clear();
m_iconSize = iconSize;
m_iconAlpha = metaData("iconAlpha").toInt();
if (m_iconAlpha)
m_iconAlpha = (m_iconAlpha << 24) | 0xffffff;
QImage img;
KConfigGroup group( KGlobal::config(), "PreviewSettings" );
// ### KFMI
bool kfmiThumb = false;
if (group.readBoolEntry( "UseFileThumbnails", true )) {
KService::Ptr service =
KServiceTypeProfile::preferredService( m_mimeType, "KFilePlugin");
if ( service && service->isValid() && /*url.isLocalFile() && */
service->property("SupportsThumbnail").toBool())
{
KFileMetaInfo info(url.path(), m_mimeType, KFileMetaInfo::Thumbnail);
if (info.isValid())
{
KFileMetaInfoItem item = info.item(KFileMimeTypeInfo::Thumbnail);
if (item.isValid() && item.value().type() == QVariant::Image)
{
img = item.value().toImage();
kdDebug(7115) << "using KFMI for the thumbnail\n";
kfmiThumb = true;
}
}
}
}
ThumbCreator::Flags flags = ThumbCreator::None;
if (!kfmiThumb)
{
kdDebug(7115) << "using thumb creator for the thumbnail\n";
QString plugin = metaData("plugin");
#ifdef THUMBNAIL_HACK
if (plugin.isEmpty())
{
KTrader::OfferList plugins = KTrader::self()->query("ThumbCreator");
QMap<QString, KService::Ptr> mimeMap;
for (KTrader::OfferList::ConstIterator it = plugins.begin(); it != plugins.end(); ++it)
{
QStringList mimeTypes = (*it)->property("MimeTypes").toStringList();
for (QStringList::ConstIterator mt = mimeTypes.begin(); mt != mimeTypes.end(); ++mt)
{
if ((*mt)==m_mimeType)
{
plugin=(*it)->library();
break;
}
}
if (!plugin.isEmpty())
break;
}
}
kdDebug(7115) << "Guess plugin: " << plugin << endl;
#endif
if (plugin.isEmpty())
{
error(KIO::ERR_INTERNAL, i18n("No plugin specified."));
return;
}
ThumbCreator *creator = m_creators[plugin];
if (!creator)
{
// Don't use KLibFactory here, this is not a QObject and
// neither is ThumbCreator
KLibrary *library = KLibLoader::self()->library(QFile::encodeName(plugin));
if (library)
{
newCreator create = (newCreator)library->symbol("new_creator");
if (create)
creator = create();
}
if (!creator)
{
error(KIO::ERR_INTERNAL, i18n("Cannot load ThumbCreator %1").arg(plugin));
return;
}
m_creators.insert(plugin, creator);
}
if (!creator->create(url.path(), m_width, m_height, img))
{
error(KIO::ERR_INTERNAL, i18n("Cannot create thumbnail for %1").arg(url.path()));
return;
}
flags = creator->flags();
}
if (img.width() > m_width || img.height() > m_height)
{
double imgRatio = (double)img.height() / (double)img.width();
if (imgRatio > (double)m_height / (double)m_width)
img = img.smoothScale( int(QMAX((double)m_height / imgRatio, 1)), m_height);
else
img = img.smoothScale(m_width, int(QMAX((double)m_width * imgRatio, 1)));
}
// ### FIXME
#ifndef USE_KINSTANCE
if (flags & ThumbCreator::DrawFrame)
{
QPixmap pix;
pix.convertFromImage(img);
int x2 = pix.width() - 1;
int y2 = pix.height() - 1;
// paint a black rectangle around the "page"
QPainter p;
p.begin( &pix );
p.setPen( QColor( 48, 48, 48 ));
p.drawLine( x2, 0, x2, y2 );
p.drawLine( 0, y2, x2, y2 );
p.setPen( QColor( 215, 215, 215 ));
p.drawLine( 0, 0, x2, 0 );
p.drawLine( 0, 0, 0, y2 );
p.end();
const QBitmap *mask = pix.mask();
if ( mask ) // need to update it so we can see the frame
{
QBitmap bitmap( *mask );
QPainter painter;
painter.begin( &bitmap );
painter.drawLine( x2, 0, x2, y2 );
painter.drawLine( 0, y2, x2, y2 );
painter.drawLine( 0, 0, x2, 0 );
painter.drawLine( 0, 0, 0, y2 );
painter.end();
pix.setMask( bitmap );
}
img = pix.convertToImage();
}
#endif
if ((flags & ThumbCreator::BlendIcon) && KGlobal::iconLoader()->alphaBlending(KIcon::Desktop))
{
// blending the mimetype icon in
QImage icon = getIcon();
int x = img.width() - icon.width() - 4;
x = QMAX( x, 0 );
int y = img.height() - icon.height() - 6;
y = QMAX( y, 0 );
KImageEffect::blendOnLower( x, y, icon, img );
}
if (img.isNull())
{
error(KIO::ERR_INTERNAL, i18n("Failed to create a thumbnail."));
return;
}
const QString shmid = metaData("shmid");
if (shmid.isEmpty())
{
#ifdef THUMBNAIL_HACK
if (direct)
{
// If thumbnail was called directly from Konqueror, then the image needs to be raw
//kdDebug(7115) << "RAW IMAGE TO STREAM" << endl;
QBuffer buf;
if (!buf.open(IO_WriteOnly))
{
error(KIO::ERR_INTERNAL, i18n("Could not write image."));
return;
}
img.save(&buf,"PNG");
buf.close();
data(buf.buffer());
}
else
#endif
{
QByteArray imgData;
QDataStream stream( imgData, IO_WriteOnly );
//kdDebug(7115) << "IMAGE TO STREAM" << endl;
stream << img;
data(imgData);
}
}
else
{
QByteArray imgData;
QDataStream stream( imgData, IO_WriteOnly );
//kdDebug(7115) << "IMAGE TO SHMID" << endl;
void *shmaddr = shmat(shmid.toInt(), 0, 0);
if (shmaddr == (void *)-1)
{
error(KIO::ERR_INTERNAL, i18n("Failed to attach to shared memory segment %1").arg(shmid));
return;
}
if (img.width() * img.height() > m_width * m_height)
{
error(KIO::ERR_INTERNAL, i18n("Image is too big for the shared memory segment"));
shmdt((char*)shmaddr);
return;
}
if( img.depth() != 32 ) // KIO::PreviewJob and this code below completely
img = img.convertDepth( 32 ); // ignores colortable :-/, so make sure there is none
stream << img.width() << img.height() << img.depth()
<< img.hasAlphaBuffer();
memcpy(shmaddr, img.bits(), img.numBytes());
shmdt((char*)shmaddr);
data(imgData);
}
finished();
}
const QImage& ThumbnailProtocol::getIcon()
{
QImage* icon = m_iconDict.find(m_mimeType);
if ( !icon ) // generate it!
{
icon = new QImage( KMimeType::mimeType(m_mimeType)->pixmap( KIcon::Desktop, m_iconSize ).convertToImage() );
icon->setAlphaBuffer( true );
int w = icon->width();
int h = icon->height();
for ( int y = 0; y < h; y++ )
{
QRgb *line = (QRgb *) icon->scanLine( y );
for ( int x = 0; x < w; x++ )
line[x] &= m_iconAlpha; // transparency
}
m_iconDict.insert( m_mimeType, icon );
}
return *icon;
}