kpropertiesdialog.cpp

00001 /* This file is part of the KDE project
00002 
00003    Copyright (C) 1998, 1999 Torben Weis <weis@kde.org>
00004    Copyright (c) 1999, 2000 Preston Brown <pbrown@kde.org>
00005    Copyright (c) 2000 Simon Hausmann <hausmann@kde.org>
00006    Copyright (c) 2000 David Faure <faure@kde.org>
00007    Copyright (c) 2003 Waldo Bastian <bastian@kde.org>
00008 
00009    This library is free software; you can redistribute it and/or
00010    modify it under the terms of the GNU Library General Public
00011    License as published by the Free Software Foundation; either
00012    version 2 of the License, or (at your option) any later version.
00013 
00014    This library is distributed in the hope that it will be useful,
00015    but WITHOUT ANY WARRANTY; without even the implied warranty of
00016    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00017    Library General Public License for more details.
00018 
00019    You should have received a copy of the GNU Library General Public License
00020    along with this library; see the file COPYING.LIB.  If not, write to
00021    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00022    Boston, MA 02110-1301, USA.
00023 */
00024 
00025 /*
00026  * kpropertiesdialog.cpp
00027  * View/Edit Properties of files, locally or remotely
00028  *
00029  * some FilePermissionsPropsPlugin-changes by
00030  *  Henner Zeller <zeller@think.de>
00031  * some layout management by
00032  *  Bertrand Leconte <B.Leconte@mail.dotcom.fr>
00033  * the rest of the layout management, bug fixes, adaptation to libkio,
00034  * template feature by
00035  *  David Faure <faure@kde.org>
00036  * More layout, cleanups, and fixes by
00037  *  Preston Brown <pbrown@kde.org>
00038  * Plugin capability, cleanups and port to KDialogBase by
00039  *  Simon Hausmann <hausmann@kde.org>
00040  * KDesktopPropsPlugin by
00041  *  Waldo Bastian <bastian@kde.org>
00042  */
00043 
00044 #include <config.h>
00045 extern "C" {
00046 #include <pwd.h>
00047 #include <grp.h>
00048 #include <time.h>
00049 #include <sys/types.h>
00050 }
00051 #include <unistd.h>
00052 #include <errno.h>
00053 #include <assert.h>
00054 #include <algorithm>
00055 #include <functional>
00056 
00057 #include <qfile.h>
00058 #include <qdir.h>
00059 #include <qlabel.h>
00060 #include <qpushbutton.h>
00061 #include <qcheckbox.h>
00062 #include <qstrlist.h>
00063 #include <qstringlist.h>
00064 #include <qtextstream.h>
00065 #include <qpainter.h>
00066 #include <qlayout.h>
00067 #include <qcombobox.h>
00068 #include <qgroupbox.h>
00069 #include <qwhatsthis.h>
00070 #include <qtooltip.h>
00071 #include <qstyle.h>
00072 #include <qprogressbar.h>
00073 #include <qvbox.h>
00074 #include <qvaluevector.h>
00075 
00076 #ifdef USE_POSIX_ACL
00077 extern "C" {
00078 #include <sys/param.h>
00079 #ifdef HAVE_SYS_MOUNT_H
00080 #include <sys/mount.h>
00081 #endif
00082 #ifdef HAVE_SYS_XATTR_H
00083 #include <sys/xattr.h>
00084 #endif
00085 }
00086 #endif
00087 
00088 #include <kapplication.h>
00089 #include <kdialog.h>
00090 #include <kdirsize.h>
00091 #include <kdirwatch.h>
00092 #include <kdirnotify_stub.h>
00093 #include <kdiskfreesp.h>
00094 #include <kdebug.h>
00095 #include <kdesktopfile.h>
00096 #include <kicondialog.h>
00097 #include <kurl.h>
00098 #include <kurlrequester.h>
00099 #include <klocale.h>
00100 #include <kglobal.h>
00101 #include <kglobalsettings.h>
00102 #include <kstandarddirs.h>
00103 #include <kio/job.h>
00104 #include <kio/chmodjob.h>
00105 #include <kio/renamedlg.h>
00106 #include <kio/netaccess.h>
00107 #include <kio/kservicetypefactory.h>
00108 #include <kfiledialog.h>
00109 #include <kmimetype.h>
00110 #include <kmountpoint.h>
00111 #include <kiconloader.h>
00112 #include <kmessagebox.h>
00113 #include <kservice.h>
00114 #include <kcompletion.h>
00115 #include <klineedit.h>
00116 #include <kseparator.h>
00117 #include <ksqueezedtextlabel.h>
00118 #include <klibloader.h>
00119 #include <ktrader.h>
00120 #include <kparts/componentfactory.h>
00121 #include <kmetaprops.h>
00122 #include <kpreviewprops.h>
00123 #include <kprocess.h>
00124 #include <krun.h>
00125 #include <klistview.h>
00126 #include <kacl.h>
00127 #include "kfilesharedlg.h"
00128 
00129 #include "kpropertiesdesktopbase.h"
00130 #include "kpropertiesdesktopadvbase.h"
00131 #include "kpropertiesmimetypebase.h"
00132 #ifdef USE_POSIX_ACL
00133 #include "kacleditwidget.h"
00134 #endif
00135 
00136 #include "kpropertiesdialog.h"
00137 
00138 #ifdef Q_WS_WIN
00139 # include <win32_utils.h>
00140 #endif
00141 
00142 static QString nameFromFileName(QString nameStr)
00143 {
00144    if ( nameStr.endsWith(".desktop") )
00145       nameStr.truncate( nameStr.length() - 8 );
00146    if ( nameStr.endsWith(".kdelnk") )
00147       nameStr.truncate( nameStr.length() - 7 );
00148    // Make it human-readable (%2F => '/', ...)
00149    nameStr = KIO::decodeFileName( nameStr );
00150    return nameStr;
00151 }
00152 
00153 mode_t KFilePermissionsPropsPlugin::fperm[3][4] = {
00154         {S_IRUSR, S_IWUSR, S_IXUSR, S_ISUID},
00155         {S_IRGRP, S_IWGRP, S_IXGRP, S_ISGID},
00156         {S_IROTH, S_IWOTH, S_IXOTH, S_ISVTX}
00157     };
00158 
00159 class KPropertiesDialog::KPropertiesDialogPrivate
00160 {
00161 public:
00162   KPropertiesDialogPrivate()
00163   {
00164     m_aborted = false;
00165     fileSharePage = 0;
00166   }
00167   ~KPropertiesDialogPrivate()
00168   {
00169   }
00170   bool m_aborted:1;
00171   QWidget* fileSharePage;
00172 };
00173 
00174 KPropertiesDialog::KPropertiesDialog (KFileItem* item,
00175                                       QWidget* parent, const char* name,
00176                                       bool modal, bool autoShow)
00177   : KDialogBase (KDialogBase::Tabbed, i18n( "Properties for %1" ).arg(KIO::decodeFileName(item->url().fileName())),
00178                  KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok,
00179                  parent, name, modal)
00180 {
00181   d = new KPropertiesDialogPrivate;
00182   assert( item );
00183   m_items.append( new KFileItem(*item) ); // deep copy
00184 
00185   m_singleUrl = item->url();
00186   assert(!m_singleUrl.isEmpty());
00187 
00188   init (modal, autoShow);
00189 }
00190 
00191 KPropertiesDialog::KPropertiesDialog (const QString& title,
00192                                       QWidget* parent, const char* name, bool modal)
00193   : KDialogBase (KDialogBase::Tabbed, i18n ("Properties for %1").arg(title),
00194                  KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok,
00195                  parent, name, modal)
00196 {
00197   d = new KPropertiesDialogPrivate;
00198 
00199   init (modal, false);
00200 }
00201 
00202 KPropertiesDialog::KPropertiesDialog (KFileItemList _items,
00203                                       QWidget* parent, const char* name,
00204                                       bool modal, bool autoShow)
00205   : KDialogBase (KDialogBase::Tabbed,
00206                  // TODO: replace <never used> with "Properties for 1 item". It's very confusing how it has to be translated otherwise
00207                  // (empty translation before the "\n" is not allowed by msgfmt...)
00208          _items.count()>1 ? i18n( "<never used>","Properties for %n Selected Items",_items.count()) :
00209          i18n( "Properties for %1" ).arg(KIO::decodeFileName(_items.first()->url().fileName())),
00210                  KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok,
00211                  parent, name, modal)
00212 {
00213   d = new KPropertiesDialogPrivate;
00214 
00215   assert( !_items.isEmpty() );
00216   m_singleUrl = _items.first()->url();
00217   assert(!m_singleUrl.isEmpty());
00218 
00219   KFileItemListIterator it ( _items );
00220   // Deep copy
00221   for ( ; it.current(); ++it )
00222       m_items.append( new KFileItem( **it ) );
00223 
00224   init (modal, autoShow);
00225 }
00226 
00227 #ifndef KDE_NO_COMPAT
00228 KPropertiesDialog::KPropertiesDialog (const KURL& _url, mode_t /* _mode is now unused */,
00229                                       QWidget* parent, const char* name,
00230                                       bool modal, bool autoShow)
00231   : KDialogBase (KDialogBase::Tabbed,
00232          i18n( "Properties for %1" ).arg(KIO::decodeFileName(_url.fileName())),
00233                  KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok,
00234                  parent, name, modal),
00235   m_singleUrl( _url )
00236 {
00237   d = new KPropertiesDialogPrivate;
00238 
00239   KIO::UDSEntry entry;
00240 
00241   KIO::NetAccess::stat(_url, entry, parent);
00242 
00243   m_items.append( new KFileItem( entry, _url ) );
00244   init (modal, autoShow);
00245 }
00246 #endif
00247 
00248 KPropertiesDialog::KPropertiesDialog (const KURL& _url,
00249                                       QWidget* parent, const char* name,
00250                                       bool modal, bool autoShow)
00251   : KDialogBase (KDialogBase::Tabbed,
00252          i18n( "Properties for %1" ).arg(KIO::decodeFileName(_url.fileName())),
00253                  KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok,
00254                  parent, name, modal),
00255   m_singleUrl( _url )
00256 {
00257   d = new KPropertiesDialogPrivate;
00258 
00259   KIO::UDSEntry entry;
00260 
00261   KIO::NetAccess::stat(_url, entry, parent);
00262 
00263   m_items.append( new KFileItem( entry, _url ) );
00264   init (modal, autoShow);
00265 }
00266 
00267 KPropertiesDialog::KPropertiesDialog (const KURL& _tempUrl, const KURL& _currentDir,
00268                                       const QString& _defaultName,
00269                                       QWidget* parent, const char* name,
00270                                       bool modal, bool autoShow)
00271   : KDialogBase (KDialogBase::Tabbed,
00272          i18n( "Properties for %1" ).arg(KIO::decodeFileName(_tempUrl.fileName())),
00273                  KDialogBase::Ok | KDialogBase::Cancel, KDialogBase::Ok,
00274                  parent, name, modal),
00275 
00276   m_singleUrl( _tempUrl ),
00277   m_defaultName( _defaultName ),
00278   m_currentDir( _currentDir )
00279 {
00280   d = new KPropertiesDialogPrivate;
00281 
00282   assert(!m_singleUrl.isEmpty());
00283 
00284   // Create the KFileItem for the _template_ file, in order to read from it.
00285   m_items.append( new KFileItem( KFileItem::Unknown, KFileItem::Unknown, m_singleUrl ) );
00286   init (modal, autoShow);
00287 }
00288 
00289 bool KPropertiesDialog::showDialog(KFileItem* item, QWidget* parent,
00290                                    const char* name, bool modal)
00291 {
00292 #ifdef Q_WS_WIN
00293   QString localPath = item->localPath();
00294   if (!localPath.isEmpty())
00295     return showWin32FilePropertyDialog(localPath);
00296 #endif
00297   new KPropertiesDialog(item, parent, name, modal);
00298   return true;
00299 }
00300 
00301 bool KPropertiesDialog::showDialog(const KURL& _url, QWidget* parent,
00302                                    const char* name, bool modal)
00303 {
00304 #ifdef Q_WS_WIN
00305   if (_url.isLocalFile())
00306     return showWin32FilePropertyDialog( _url.path() );
00307 #endif
00308   new KPropertiesDialog(_url, parent, name, modal);
00309   return true;
00310 }
00311 
00312 bool KPropertiesDialog::showDialog(const KFileItemList& _items, QWidget* parent,
00313                                    const char* name, bool modal)
00314 {
00315   if (_items.count()==1)
00316     return KPropertiesDialog::showDialog(_items.getFirst(), parent, name, modal);
00317   new KPropertiesDialog(_items, parent, name, modal);
00318   return true;
00319 }
00320 
00321 void KPropertiesDialog::init (bool modal, bool autoShow)
00322 {
00323   m_pageList.setAutoDelete( true );
00324   m_items.setAutoDelete( true );
00325 
00326   insertPages();
00327 
00328   if (autoShow)
00329     {
00330       if (!modal)
00331         show();
00332       else
00333         exec();
00334     }
00335 }
00336 
00337 void KPropertiesDialog::showFileSharingPage()
00338 {
00339   if (d->fileSharePage) {
00340      showPage( pageIndex( d->fileSharePage));
00341   }
00342 }
00343 
00344 void KPropertiesDialog::setFileSharingPage(QWidget* page) {
00345   d->fileSharePage = page;
00346 }
00347 
00348 
00349 void KPropertiesDialog::setFileNameReadOnly( bool ro )
00350 {
00351     KPropsDlgPlugin *it;
00352 
00353     for ( it=m_pageList.first(); it != 0L; it=m_pageList.next() )
00354     {
00355         KFilePropsPlugin* plugin = dynamic_cast<KFilePropsPlugin*>(it);
00356         if ( plugin ) {
00357             plugin->setFileNameReadOnly( ro );
00358             break;
00359         }
00360     }
00361 }
00362 
00363 void KPropertiesDialog::slotStatResult( KIO::Job * )
00364 {
00365 }
00366 
00367 KPropertiesDialog::~KPropertiesDialog()
00368 {
00369   m_pageList.clear();
00370   delete d;
00371 }
00372 
00373 void KPropertiesDialog::insertPlugin (KPropsDlgPlugin* plugin)
00374 {
00375   connect (plugin, SIGNAL (changed ()),
00376            plugin, SLOT (setDirty ()));
00377 
00378   m_pageList.append (plugin);
00379 }
00380 
00381 bool KPropertiesDialog::canDisplay( KFileItemList _items )
00382 {
00383   // TODO: cache the result of those calls. Currently we parse .desktop files far too many times
00384   return KFilePropsPlugin::supports( _items ) ||
00385          KFilePermissionsPropsPlugin::supports( _items ) ||
00386          KDesktopPropsPlugin::supports( _items ) ||
00387          KBindingPropsPlugin::supports( _items ) ||
00388          KURLPropsPlugin::supports( _items ) ||
00389          KDevicePropsPlugin::supports( _items ) ||
00390          KFileMetaPropsPlugin::supports( _items ) ||
00391          KPreviewPropsPlugin::supports( _items );
00392 }
00393 
00394 void KPropertiesDialog::slotOk()
00395 {
00396   KPropsDlgPlugin *page;
00397   d->m_aborted = false;
00398 
00399   KFilePropsPlugin * filePropsPlugin = 0L;
00400   if ( m_pageList.first()->isA("KFilePropsPlugin") )
00401     filePropsPlugin = static_cast<KFilePropsPlugin *>(m_pageList.first());
00402 
00403   // If any page is dirty, then set the main one (KFilePropsPlugin) as
00404   // dirty too. This is what makes it possible to save changes to a global
00405   // desktop file into a local one. In other cases, it doesn't hurt.
00406   for ( page = m_pageList.first(); page != 0L; page = m_pageList.next() )
00407     if ( page->isDirty() && filePropsPlugin )
00408     {
00409         filePropsPlugin->setDirty();
00410         break;
00411     }
00412 
00413   // Apply the changes in the _normal_ order of the tabs now
00414   // This is because in case of renaming a file, KFilePropsPlugin will call
00415   // KPropertiesDialog::rename, so other tab will be ok with whatever order
00416   // BUT for file copied from templates, we need to do the renaming first !
00417   for ( page = m_pageList.first(); page != 0L && !d->m_aborted; page = m_pageList.next() )
00418     if ( page->isDirty() )
00419     {
00420       kdDebug( 250 ) << "applying changes for " << page->className() << endl;
00421       page->applyChanges();
00422       // applyChanges may change d->m_aborted.
00423     }
00424     else
00425       kdDebug( 250 ) << "skipping page " << page->className() << endl;
00426 
00427   if ( !d->m_aborted && filePropsPlugin )
00428     filePropsPlugin->postApplyChanges();
00429 
00430   if ( !d->m_aborted )
00431   {
00432     emit applied();
00433     emit propertiesClosed();
00434     deleteLater();
00435     accept();
00436   } // else, keep dialog open for user to fix the problem.
00437 }
00438 
00439 void KPropertiesDialog::slotCancel()
00440 {
00441   emit canceled();
00442   emit propertiesClosed();
00443 
00444   deleteLater();
00445   done( Rejected );
00446 }
00447 
00448 void KPropertiesDialog::insertPages()
00449 {
00450   if (m_items.isEmpty())
00451     return;
00452 
00453   if ( KFilePropsPlugin::supports( m_items ) )
00454   {
00455     KPropsDlgPlugin *p = new KFilePropsPlugin( this );
00456     insertPlugin (p);
00457   }
00458 
00459   if ( KFilePermissionsPropsPlugin::supports( m_items ) )
00460   {
00461     KPropsDlgPlugin *p = new KFilePermissionsPropsPlugin( this );
00462     insertPlugin (p);
00463   }
00464 
00465   if ( KDesktopPropsPlugin::supports( m_items ) )
00466   {
00467     KPropsDlgPlugin *p = new KDesktopPropsPlugin( this );
00468     insertPlugin (p);
00469   }
00470 
00471   if ( KBindingPropsPlugin::supports( m_items ) )
00472   {
00473     KPropsDlgPlugin *p = new KBindingPropsPlugin( this );
00474     insertPlugin (p);
00475   }
00476 
00477   if ( KURLPropsPlugin::supports( m_items ) )
00478   {
00479     KPropsDlgPlugin *p = new KURLPropsPlugin( this );
00480     insertPlugin (p);
00481   }
00482 
00483   if ( KDevicePropsPlugin::supports( m_items ) )
00484   {
00485     KPropsDlgPlugin *p = new KDevicePropsPlugin( this );
00486     insertPlugin (p);
00487   }
00488 
00489   if ( KFileMetaPropsPlugin::supports( m_items ) )
00490   {
00491     KPropsDlgPlugin *p = new KFileMetaPropsPlugin( this );
00492     insertPlugin (p);
00493   }
00494 
00495   if ( KPreviewPropsPlugin::supports( m_items ) )
00496   {
00497     KPropsDlgPlugin *p = new KPreviewPropsPlugin( this );
00498     insertPlugin (p);
00499   }
00500 
00501   if ( kapp->authorizeKAction("sharefile") &&
00502        KFileSharePropsPlugin::supports( m_items ) )
00503   {
00504     KPropsDlgPlugin *p = new KFileSharePropsPlugin( this );
00505     insertPlugin (p);
00506   }
00507 
00508   //plugins
00509 
00510   if ( m_items.count() != 1 )
00511     return;
00512 
00513   KFileItem *item = m_items.first();
00514   QString mimetype = item->mimetype();
00515 
00516   if ( mimetype.isEmpty() )
00517     return;
00518 
00519   QString query = QString::fromLatin1(
00520       "('KPropsDlg/Plugin' in ServiceTypes) and "
00521       "((not exist [X-KDE-Protocol]) or "
00522       " ([X-KDE-Protocol] == '%1'  )   )"          ).arg(item->url().protocol());
00523 
00524   kdDebug( 250 ) << "trader query: " << query << endl;
00525   KTrader::OfferList offers = KTrader::self()->query( mimetype, query );
00526   KTrader::OfferList::ConstIterator it = offers.begin();
00527   KTrader::OfferList::ConstIterator end = offers.end();
00528   for (; it != end; ++it )
00529   {
00530     KPropsDlgPlugin *plugin = KParts::ComponentFactory
00531         ::createInstanceFromLibrary<KPropsDlgPlugin>( (*it)->library().local8Bit().data(),
00532                                                       this,
00533                                                       (*it)->name().latin1() );
00534     if ( !plugin )
00535         continue;
00536 
00537     insertPlugin( plugin );
00538   }
00539 }
00540 
00541 void KPropertiesDialog::updateUrl( const KURL& _newUrl )
00542 {
00543   Q_ASSERT( m_items.count() == 1 );
00544   kdDebug(250) << "KPropertiesDialog::updateUrl (pre)" << _newUrl.url() << endl;
00545   KURL newUrl = _newUrl;
00546   emit saveAs(m_singleUrl, newUrl);
00547   kdDebug(250) << "KPropertiesDialog::updateUrl (post)" << newUrl.url() << endl;
00548 
00549   m_singleUrl = newUrl;
00550   m_items.first()->setURL( newUrl );
00551   assert(!m_singleUrl.isEmpty());
00552   // If we have an Desktop page, set it dirty, so that a full file is saved locally
00553   // Same for a URL page (because of the Name= hack)
00554   for ( QPtrListIterator<KPropsDlgPlugin> it(m_pageList); it.current(); ++it )
00555    if ( it.current()->isA("KExecPropsPlugin") || // KDE4 remove me
00556         it.current()->isA("KURLPropsPlugin") ||
00557         it.current()->isA("KDesktopPropsPlugin"))
00558    {
00559      //kdDebug(250) << "Setting page dirty" << endl;
00560      it.current()->setDirty();
00561      break;
00562    }
00563 }
00564 
00565 void KPropertiesDialog::rename( const QString& _name )
00566 {
00567   Q_ASSERT( m_items.count() == 1 );
00568   kdDebug(250) << "KPropertiesDialog::rename " << _name << endl;
00569   KURL newUrl;
00570   // if we're creating from a template : use currentdir
00571   if ( !m_currentDir.isEmpty() )
00572   {
00573     newUrl = m_currentDir;
00574     newUrl.addPath( _name );
00575   }
00576   else
00577   {
00578     QString tmpurl = m_singleUrl.url();
00579     if ( tmpurl.at(tmpurl.length() - 1) == '/')
00580       // It's a directory, so strip the trailing slash first
00581       tmpurl.truncate( tmpurl.length() - 1);
00582     newUrl = tmpurl;
00583     newUrl.setFileName( _name );
00584   }
00585   updateUrl( newUrl );
00586 }
00587 
00588 void KPropertiesDialog::abortApplying()
00589 {
00590   d->m_aborted = true;
00591 }
00592 
00593 class KPropsDlgPlugin::KPropsDlgPluginPrivate
00594 {
00595 public:
00596   KPropsDlgPluginPrivate()
00597   {
00598   }
00599   ~KPropsDlgPluginPrivate()
00600   {
00601   }
00602 
00603   bool m_bDirty;
00604 };
00605 
00606 KPropsDlgPlugin::KPropsDlgPlugin( KPropertiesDialog *_props )
00607 : QObject( _props, 0L )
00608 {
00609   d = new KPropsDlgPluginPrivate;
00610   properties = _props;
00611   fontHeight = 2*properties->fontMetrics().height();
00612   d->m_bDirty = false;
00613 }
00614 
00615 KPropsDlgPlugin::~KPropsDlgPlugin()
00616 {
00617   delete d;
00618 }
00619 
00620 bool KPropsDlgPlugin::isDesktopFile( KFileItem * _item )
00621 {
00622   // only local files
00623   bool isLocal;
00624   KURL url = _item->mostLocalURL( isLocal );
00625   if ( !isLocal )
00626     return false;
00627 
00628   // only regular files
00629   if ( !S_ISREG( _item->mode() ) )
00630     return false;
00631 
00632   QString t( url.path() );
00633 
00634   // only if readable
00635   FILE *f = fopen( QFile::encodeName(t), "r" );
00636   if ( f == 0L )
00637     return false;
00638   fclose(f);
00639 
00640   // return true if desktop file
00641   return ( _item->mimetype() == "application/x-desktop" );
00642 }
00643 
00644 void KPropsDlgPlugin::setDirty( bool b )
00645 {
00646   d->m_bDirty = b;
00647 }
00648 
00649 void KPropsDlgPlugin::setDirty()
00650 {
00651   d->m_bDirty = true;
00652 }
00653 
00654 bool KPropsDlgPlugin::isDirty() const
00655 {
00656   return d->m_bDirty;
00657 }
00658 
00659 void KPropsDlgPlugin::applyChanges()
00660 {
00661   kdWarning(250) << "applyChanges() not implemented in page !" << endl;
00662 }
00663 
00665 
00666 class KFilePropsPlugin::KFilePropsPluginPrivate
00667 {
00668 public:
00669   KFilePropsPluginPrivate()
00670   {
00671     dirSizeJob = 0L;
00672     dirSizeUpdateTimer = 0L;
00673     m_lined = 0;
00674   }
00675   ~KFilePropsPluginPrivate()
00676   {
00677     if ( dirSizeJob )
00678       dirSizeJob->kill();
00679   }
00680 
00681   KDirSize * dirSizeJob;
00682   QTimer *dirSizeUpdateTimer;
00683   QFrame *m_frame;
00684   bool bMultiple;
00685   bool bIconChanged;
00686   bool bKDesktopMode;
00687   bool bDesktopFile;
00688   QLabel *m_freeSpaceLabel;
00689   QString mimeType;
00690   QString oldFileName;
00691   KLineEdit* m_lined;
00692 };
00693 
00694 KFilePropsPlugin::KFilePropsPlugin( KPropertiesDialog *_props )
00695   : KPropsDlgPlugin( _props )
00696 {
00697   d = new KFilePropsPluginPrivate;
00698   d->bMultiple = (properties->items().count() > 1);
00699   d->bIconChanged = false;
00700   d->bKDesktopMode = (QCString(qApp->name()) == "kdesktop"); // nasty heh?
00701   d->bDesktopFile = KDesktopPropsPlugin::supports(properties->items());
00702   kdDebug(250) << "KFilePropsPlugin::KFilePropsPlugin bMultiple=" << d->bMultiple << endl;
00703 
00704   // We set this data from the first item, and we'll
00705   // check that the other items match against it, resetting when not.
00706   bool isLocal;
00707   KFileItem * item = properties->item();
00708   KURL url = item->mostLocalURL( isLocal );
00709   bool isReallyLocal = item->url().isLocalFile();
00710   bool bDesktopFile = isDesktopFile(item);
00711   kdDebug() << "url=" << url << " bDesktopFile=" << bDesktopFile << " isLocal=" << isLocal << " isReallyLocal=" << isReallyLocal << endl;
00712   mode_t mode = item->mode();
00713   bool hasDirs = item->isDir() && !item->isLink();
00714   bool hasRoot = url.path() == QString::fromLatin1("/");
00715   QString iconStr = KMimeType::iconForURL(url, mode);
00716   QString directory = properties->kurl().directory();
00717   QString protocol = properties->kurl().protocol();
00718   QString mimeComment = item->mimeComment();
00719   d->mimeType = item->mimetype();
00720   bool hasTotalSize;
00721   KIO::filesize_t totalSize = item->size(hasTotalSize);
00722   QString magicMimeComment;
00723   if ( isLocal ) {
00724       KMimeType::Ptr magicMimeType = KMimeType::findByFileContent( url.path() );
00725       if ( magicMimeType->name() != KMimeType::defaultMimeType() )
00726           magicMimeComment = magicMimeType->comment();
00727   }
00728 
00729   // Those things only apply to 'single file' mode
00730   QString filename = QString::null;
00731   bool isTrash = false;
00732   bool isDevice = false;
00733   m_bFromTemplate = false;
00734 
00735   // And those only to 'multiple' mode
00736   uint iDirCount = hasDirs ? 1 : 0;
00737   uint iFileCount = 1-iDirCount;
00738 
00739   d->m_frame = properties->addPage (i18n("&General"));
00740 
00741   QVBoxLayout *vbl = new QVBoxLayout( d->m_frame, 0,
00742                                       KDialog::spacingHint(), "vbl");
00743   QGridLayout *grid = new QGridLayout(0, 3); // unknown rows
00744   grid->setColStretch(0, 0);
00745   grid->setColStretch(1, 0);
00746   grid->setColStretch(2, 1);
00747   grid->addColSpacing(1, KDialog::spacingHint());
00748   vbl->addLayout(grid);
00749   int curRow = 0;
00750 
00751   if ( !d->bMultiple )
00752   {
00753     QString path;
00754     if ( !m_bFromTemplate ) {
00755       isTrash = ( properties->kurl().protocol().find( "trash", 0, false)==0 );
00756       if ( properties->kurl().protocol().find("device", 0, false)==0)
00757             isDevice = true;
00758       // Extract the full name, but without file: for local files
00759       if ( isReallyLocal )
00760         path = properties->kurl().path();
00761       else
00762         path = properties->kurl().prettyURL();
00763     } else {
00764       path = properties->currentDir().path(1) + properties->defaultName();
00765       directory = properties->currentDir().prettyURL();
00766     }
00767 
00768     if (KExecPropsPlugin::supports(properties->items()) || // KDE4 remove me
00769         d->bDesktopFile ||
00770         KBindingPropsPlugin::supports(properties->items())) {
00771       determineRelativePath( path );
00772     }
00773 
00774     // Extract the file name only
00775     filename = properties->defaultName();
00776     if ( filename.isEmpty() ) { // no template
00777       filename = item->name(); // this gives support for UDS_NAME, e.g. for kio_trash or kio_system
00778     } else {
00779       m_bFromTemplate = true;
00780       setDirty(); // to enforce that the copy happens
00781     }
00782     d->oldFileName = filename;
00783 
00784     // Make it human-readable
00785     filename = nameFromFileName( filename );
00786 
00787     if ( d->bKDesktopMode && d->bDesktopFile ) {
00788         KDesktopFile config( url.path(), true /* readonly */ );
00789         if ( config.hasKey( "Name" ) ) {
00790             filename = config.readName();
00791         }
00792     }
00793 
00794     oldName = filename;
00795   }
00796   else
00797   {
00798     // Multiple items: see what they have in common
00799     KFileItemList items = properties->items();
00800     KFileItemListIterator it( items );
00801     for ( ++it /*no need to check the first one again*/ ; it.current(); ++it )
00802     {
00803       KURL url = (*it)->url();
00804       kdDebug(250) << "KFilePropsPlugin::KFilePropsPlugin " << url.prettyURL() << endl;
00805       // The list of things we check here should match the variables defined
00806       // at the beginning of this method.
00807       if ( url.isLocalFile() != isLocal )
00808         isLocal = false; // not all local
00809       if ( bDesktopFile && isDesktopFile(*it) != bDesktopFile )
00810         bDesktopFile = false; // not all desktop files
00811       if ( (*it)->mode() != mode )
00812         mode = (mode_t)0;
00813       if ( KMimeType::iconForURL(url, mode) != iconStr )
00814         iconStr = "kmultiple";
00815       if ( url.directory() != directory )
00816         directory = QString::null;
00817       if ( url.protocol() != protocol )
00818         protocol = QString::null;
00819       if ( !mimeComment.isNull() && (*it)->mimeComment() != mimeComment )
00820         mimeComment = QString::null;
00821       if ( isLocal && !magicMimeComment.isNull() ) {
00822           KMimeType::Ptr magicMimeType = KMimeType::findByFileContent( url.path() );
00823           if ( magicMimeType->comment() != magicMimeComment )
00824               magicMimeComment = QString::null;
00825       }
00826 
00827       if ( url.path() == QString::fromLatin1("/") )
00828         hasRoot = true;
00829       if ( (*it)->isDir() && !(*it)->isLink() )
00830       {
00831         iDirCount++;
00832         hasDirs = true;
00833       }
00834       else
00835       {
00836         iFileCount++;
00837     bool hasSize;
00838         totalSize += (*it)->size(hasSize);
00839     hasTotalSize = hasTotalSize || hasSize;
00840       }
00841     }
00842   }
00843 
00844   if (!isReallyLocal && !protocol.isEmpty())
00845   {
00846     directory += ' ';
00847     directory += '(';
00848     directory += protocol;
00849     directory += ')';
00850   }
00851 
00852   if ( !isDevice && !isTrash && (bDesktopFile || S_ISDIR(mode)) && !d->bMultiple /*not implemented for multiple*/ )
00853   {
00854     KIconButton *iconButton = new KIconButton( d->m_frame );
00855     int bsize = 66 + 2 * iconButton->style().pixelMetric(QStyle::PM_ButtonMargin);
00856     iconButton->setFixedSize(bsize, bsize);
00857     iconButton->setIconSize(48);
00858     iconButton->setStrictIconSize(false);
00859     // This works for everything except Device icons on unmounted devices
00860     // So we have to really open .desktop files
00861     QString iconStr = KMimeType::findByURL( url, mode )->icon( url, isLocal );
00862     if ( bDesktopFile && isLocal )
00863     {
00864       KDesktopFile config( url.path(), true );
00865       config.setDesktopGroup();
00866       iconStr = config.readEntry( "Icon" );
00867       if ( config.hasDeviceType() )
00868     iconButton->setIconType( KIcon::Desktop, KIcon::Device );
00869       else
00870     iconButton->setIconType( KIcon::Desktop, KIcon::Application );
00871     } else
00872       iconButton->setIconType( KIcon::Desktop, KIcon::FileSystem );
00873     iconButton->setIcon(iconStr);
00874     iconArea = iconButton;
00875     connect( iconButton, SIGNAL( iconChanged(QString) ),
00876              this, SLOT( slotIconChanged() ) );
00877   } else {
00878     QLabel *iconLabel = new QLabel( d->m_frame );
00879     int bsize = 66 + 2 * iconLabel->style().pixelMetric(QStyle::PM_ButtonMargin);
00880     iconLabel->setFixedSize(bsize, bsize);
00881     iconLabel->setPixmap( KGlobal::iconLoader()->loadIcon( iconStr, KIcon::Desktop, 48) );
00882     iconArea = iconLabel;
00883   }
00884   grid->addWidget(iconArea, curRow, 0, AlignLeft);
00885 
00886   if (d->bMultiple || isTrash || isDevice || hasRoot)
00887   {
00888     QLabel *lab = new QLabel(d->m_frame );
00889     if ( d->bMultiple )
00890       lab->setText( KIO::itemsSummaryString( iFileCount + iDirCount, iFileCount, iDirCount, 0, false ) );
00891     else
00892       lab->setText( filename );
00893     nameArea = lab;
00894   } else
00895   {
00896     d->m_lined = new KLineEdit( d->m_frame );
00897     d->m_lined->setText(filename);
00898     nameArea = d->m_lined;
00899     d->m_lined->setFocus();
00900 
00901     // Enhanced rename: Don't highlight the file extension.
00902     QString pattern;
00903     KServiceTypeFactory::self()->findFromPattern( filename, &pattern );
00904     if (!pattern.isEmpty() && pattern.at(0)=='*' && pattern.find('*',1)==-1)
00905       d->m_lined->setSelection(0, filename.length()-pattern.stripWhiteSpace().length()+1);
00906     else
00907     {
00908       int lastDot = filename.findRev('.');
00909       if (lastDot > 0)
00910         d->m_lined->setSelection(0, lastDot);
00911     }
00912 
00913     connect( d->m_lined, SIGNAL( textChanged( const QString & ) ),
00914              this, SLOT( nameFileChanged(const QString & ) ) );
00915   }
00916 
00917   grid->addWidget(nameArea, curRow++, 2);
00918 
00919   KSeparator* sep = new KSeparator( KSeparator::HLine, d->m_frame);
00920   grid->addMultiCellWidget(sep, curRow, curRow, 0, 2);
00921   ++curRow;
00922 
00923   QLabel *l;
00924   if ( !mimeComment.isEmpty() && !isDevice && !isTrash)
00925   {
00926     l = new QLabel(i18n("Type:"), d->m_frame );
00927 
00928     grid->addWidget(l, curRow, 0);
00929 
00930     QHBox *box = new QHBox(d->m_frame);
00931     box->setSpacing(20);
00932     l = new QLabel(mimeComment, box );
00933 
00934 #ifdef Q_WS_X11
00935     //TODO: wrap for win32 or mac?
00936     QPushButton *button = new QPushButton(box);
00937 
00938     QIconSet iconSet = SmallIconSet(QString::fromLatin1("configure"));
00939     QPixmap pixMap = iconSet.pixmap( QIconSet::Small, QIconSet::Normal );
00940     button->setIconSet( iconSet );
00941     button->setFixedSize( pixMap.width()+8, pixMap.height()+8 );
00942     if ( d->mimeType == KMimeType::defaultMimeType() )
00943        QToolTip::add(button, i18n("Create new file type"));
00944     else
00945        QToolTip::add(button, i18n("Edit file type"));
00946 
00947     connect( button, SIGNAL( clicked() ), SLOT( slotEditFileType() ));
00948 
00949     if (!kapp->authorizeKAction("editfiletype"))
00950        button->hide();
00951 #endif
00952 
00953     grid->addWidget(box, curRow++, 2);
00954   }
00955 
00956   if ( !magicMimeComment.isEmpty() && magicMimeComment != mimeComment )
00957   {
00958     l = new QLabel(i18n("Contents:"), d->m_frame );
00959     grid->addWidget(l, curRow, 0);
00960 
00961     l = new QLabel(magicMimeComment, d->m_frame );
00962     grid->addWidget(l, curRow++, 2);
00963   }
00964 
00965   if ( !directory.isEmpty() )
00966   {
00967     l = new QLabel( i18n("Location:"), d->m_frame );
00968     grid->addWidget(l, curRow, 0);
00969 
00970     l = new KSqueezedTextLabel( d->m_frame );
00971     l->setText( directory );
00972     grid->addWidget(l, curRow++, 2);
00973   }
00974 
00975   if( hasDirs || hasTotalSize ) {
00976     l = new QLabel(i18n("Size:"), d->m_frame );
00977     grid->addWidget(l, curRow, 0);
00978 
00979     m_sizeLabel = new QLabel( d->m_frame );
00980     grid->addWidget( m_sizeLabel, curRow++, 2 );
00981   } else {
00982     m_sizeLabel = 0;
00983   }
00984 
00985   if ( !hasDirs ) // Only files [and symlinks]
00986   {
00987     if(hasTotalSize) {
00988       m_sizeLabel->setText(KIO::convertSizeWithBytes(totalSize));
00989     }
00990 
00991     m_sizeDetermineButton = 0L;
00992     m_sizeStopButton = 0L;
00993   }
00994   else // Directory
00995   {
00996     QHBoxLayout * sizelay = new QHBoxLayout(KDialog::spacingHint());
00997     grid->addLayout( sizelay, curRow++, 2 );
00998 
00999     // buttons
01000     m_sizeDetermineButton = new QPushButton( i18n("Calculate"), d->m_frame );
01001     m_sizeStopButton = new QPushButton( i18n("Stop"), d->m_frame );
01002     connect( m_sizeDetermineButton, SIGNAL( clicked() ), this, SLOT( slotSizeDetermine() ) );
01003     connect( m_sizeStopButton, SIGNAL( clicked() ), this, SLOT( slotSizeStop() ) );
01004     sizelay->addWidget(m_sizeDetermineButton, 0);
01005     sizelay->addWidget(m_sizeStopButton, 0);
01006     sizelay->addStretch(10); // so that the buttons don't grow horizontally
01007 
01008     // auto-launch for local dirs only, and not for '/'
01009     if ( isLocal && !hasRoot )
01010     {
01011       m_sizeDetermineButton->setText( i18n("Refresh") );
01012       slotSizeDetermine();
01013     }
01014     else
01015       m_sizeStopButton->setEnabled( false );
01016   }
01017 
01018   if (!d->bMultiple && item->isLink()) {
01019     l = new QLabel(i18n("Points to:"), d->m_frame );
01020     grid->addWidget(l, curRow, 0);
01021 
01022     l = new KSqueezedTextLabel(item->linkDest(), d->m_frame );
01023     grid->addWidget(l, curRow++, 2);
01024   }
01025 
01026   if (!d->bMultiple) // Dates for multiple don't make much sense...
01027   {
01028     QDateTime dt;
01029     bool hasTime;
01030     time_t tim = item->time(KIO::UDS_CREATION_TIME, hasTime);
01031     if ( hasTime )
01032     {
01033       l = new QLabel(i18n("Created:"), d->m_frame );
01034       grid->addWidget(l, curRow, 0);
01035 
01036       dt.setTime_t( tim );
01037       l = new QLabel(KGlobal::locale()->formatDateTime(dt), d->m_frame );
01038       grid->addWidget(l, curRow++, 2);
01039     }
01040 
01041     tim = item->time(KIO::UDS_MODIFICATION_TIME, hasTime);
01042     if ( hasTime )
01043     {
01044       l = new QLabel(i18n("Modified:"), d->m_frame );
01045       grid->addWidget(l, curRow, 0);
01046 
01047       dt.setTime_t( tim );
01048       l = new QLabel(KGlobal::locale()->formatDateTime(dt), d->m_frame );
01049       grid->addWidget(l, curRow++, 2);
01050     }
01051 
01052     tim = item->time(KIO::UDS_ACCESS_TIME, hasTime);
01053     if ( hasTime )
01054     {
01055       l = new QLabel(i18n("Accessed:"), d->m_frame );
01056       grid->addWidget(l, curRow, 0);
01057 
01058       dt.setTime_t( tim );
01059       l = new QLabel(KGlobal::locale()->formatDateTime(dt), d->m_frame );
01060       grid->addWidget(l, curRow++, 2);
01061     }
01062   }
01063 
01064   if ( isLocal && hasDirs )  // only for directories
01065   {
01066     sep = new KSeparator( KSeparator::HLine, d->m_frame);
01067     grid->addMultiCellWidget(sep, curRow, curRow, 0, 2);
01068     ++curRow;
01069 
01070     QString mountPoint = KIO::findPathMountPoint( url.path() );
01071 
01072     if (mountPoint != "/")
01073     {
01074         l = new QLabel(i18n("Mounted on:"), d->m_frame );
01075         grid->addWidget(l, curRow, 0);
01076 
01077         l = new KSqueezedTextLabel( mountPoint, d->m_frame );
01078         grid->addWidget( l, curRow++, 2 );
01079     }
01080 
01081     l = new QLabel(i18n("Free disk space:"), d->m_frame );
01082     grid->addWidget(l, curRow, 0);
01083 
01084     d->m_freeSpaceLabel = new QLabel( d->m_frame );
01085     grid->addWidget( d->m_freeSpaceLabel, curRow++, 2 );
01086 
01087     KDiskFreeSp * job = new KDiskFreeSp;
01088     connect( job, SIGNAL( foundMountPoint( const unsigned long&, const unsigned long&,
01089              const unsigned long&, const QString& ) ),
01090              this, SLOT( slotFoundMountPoint( const unsigned long&, const unsigned long&,
01091           const unsigned long&, const QString& ) ) );
01092     job->readDF( mountPoint );
01093   }
01094 
01095   vbl->addStretch(1);
01096 }
01097 
01098 // QString KFilePropsPlugin::tabName () const
01099 // {
01100 //   return i18n ("&General");
01101 // }
01102 
01103 void KFilePropsPlugin::setFileNameReadOnly( bool ro )
01104 {
01105   if ( d->m_lined )
01106   {
01107     d->m_lined->setReadOnly( ro );
01108     if (ro)
01109     {
01110        // Don't put the initial focus on the line edit when it is ro
01111        QPushButton *button = properties->actionButton(KDialogBase::Ok);
01112        if (button)
01113           button->setFocus();
01114     }
01115   }
01116 }
01117 
01118 void KFilePropsPlugin::slotEditFileType()
01119 {
01120 #ifdef Q_WS_X11
01121   QString mime;
01122   if ( d->mimeType == KMimeType::defaultMimeType() ) {
01123     int pos = d->oldFileName.findRev( '.' );
01124     if ( pos != -1 )
01125     mime = "*" + d->oldFileName.mid(pos);
01126     else
01127     mime = "*";
01128   }
01129   else
01130     mime = d->mimeType;
01131     //TODO: wrap for win32 or mac?
01132   QString keditfiletype = QString::fromLatin1("keditfiletype");
01133   KRun::runCommand( keditfiletype
01134                     + " --parent " + QString::number( (ulong)properties->topLevelWidget()->winId())
01135                     + " " + KProcess::quote(mime),
01136                     keditfiletype, keditfiletype /*unused*/);
01137 #endif
01138 }
01139 
01140 void KFilePropsPlugin::slotIconChanged()
01141 {
01142   d->bIconChanged = true;
01143   emit changed();
01144 }
01145 
01146 void KFilePropsPlugin::nameFileChanged(const QString &text )
01147 {
01148   properties->enableButtonOK(!text.isEmpty());
01149   emit changed();
01150 }
01151 
01152 void KFilePropsPlugin::determineRelativePath( const QString & path )
01153 {
01154     // now let's make it relative
01155     QStringList dirs;
01156     if (KBindingPropsPlugin::supports(properties->items()))
01157     {
01158        m_sRelativePath =KGlobal::dirs()->relativeLocation("mime", path);
01159        if (m_sRelativePath.startsWith("/"))
01160           m_sRelativePath = QString::null;
01161     }
01162     else
01163     {
01164        m_sRelativePath =KGlobal::dirs()->relativeLocation("apps", path);
01165        if (m_sRelativePath.