/* This file is part of the KDE project
Copyright ( C ) 2003 Georg Robbers < Georg . Robbers @ urz . uni - hd . de >
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 ; version 2
of the License .
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 ; see the file COPYING . If not , write to
the Free Software Foundation , Inc . , 51 Franklin Street , Fifth Floor ,
Boston , MA 02110 - 1301 , USA .
*/
# include "arkplugin.h"
# include <tdeapplication.h>
# include <kstandarddirs.h>
# include <kmimetype.h>
# include <kdebug.h>
# include <tdeaction.h>
# include <kinstance.h>
# include <tdelocale.h>
# include <konq_popupmenu.h>
# include <tdepopupmenu.h>
# include <kgenericfactory.h>
# include <kurl.h>
# include <tdeio/netaccess.h>
# include <tqdir.h>
# include <tqcstring.h>
# include <tqsignalmapper.h>
# include <tqobject.h>
typedef KGenericFactory < ArkMenu , KonqPopupMenu > ArkMenuFactory ;
K_EXPORT_COMPONENT_FACTORY ( libarkplugin , ArkMenuFactory ( " arkplugin " ) )
ArkMenu : : ArkMenu ( KonqPopupMenu * popupmenu , const char * name , const TQStringList & /* list */ )
: KonqPopupMenuPlugin ( popupmenu , name ) ,
m_compAsMapper ( 0 ) , m_addToMapper ( 0 ) , m_conf ( 0 )
{
if ( ( TQCString ( kapp - > name ( ) ) = = " kdesktop " & & ! kapp - > authorize ( " editable_desktop_icons " ) )
| | ( TDEStandardDirs : : findExe ( " ark " ) . isNull ( ) ) )
return ;
m_conf = new TDEConfig ( " arkrc " ) ;
m_conf - > setGroup ( " ark " ) ;
if ( ! m_conf - > readBoolEntry ( " KonquerorIntegration " , true ) )
return ;
TDEGlobal : : locale ( ) - > insertCatalogue ( " ark_plugin " ) ;
extMimeTypes ( ) ;
KFileItemList itemList = popupmenu - > fileItemList ( ) ;
for ( KFileItem * item = itemList . first ( ) ; item ; item = itemList . next ( ) )
{
m_urlList . append ( item - > url ( ) ) ;
m_urlStringList . append ( item - > url ( ) . url ( ) ) ;
}
m_dir = popupmenu - > url ( ) . url ( ) + " / " ;
unsigned int itemCount = m_urlList . count ( ) ;
KFileItemListIterator it ( itemList ) ;
KFileItem * item ;
bool hasArchives = false ;
bool hasOther = false ;
while ( ( item = it . current ( ) ) ! = 0 )
{
+ + it ;
if ( m_extractMimeTypes . contains ( item - > mimetype ( ) ) )
{
hasArchives = true ;
}
else
{
hasOther = true ;
}
if ( hasArchives & & hasOther )
break ;
}
TQString ext ;
TDEActionMenu * actionMenu ;
TDEAction * action ;
if ( hasOther & & itemList . first ( ) - > name ( ) ! = " . " & & popupmenu - > protocolInfo ( ) . supportsWriting ( ) ) // don't try to compress if we right click on a folder without files selected
{
compMimeTypes ( ) ;
actionMenu = new TDEActionMenu ( i18n ( " Compress " ) , " ark " , actionCollection ( ) , " ark_compress_menu " ) ;
m_conf - > setGroup ( " ArkPlugin " ) ;
m_ext = m_conf - > readEntry ( " LastExtension " , " .tar.xz " ) ;
if ( itemCount = = 1 )
{
item = itemList . first ( ) ;
m_name = itemList . first ( ) - > name ( ) ;
action = new TDEAction ( i18n ( " Compress as %1 " ) . arg ( m_name + m_ext ) , 0 , this ,
TQ_SLOT ( slotCompressAsDefault ( ) ) , actionCollection ( ) ) ;
}
else
{
action = new TDEAction ( KMimeType : : mimeType ( m_conf - > readEntry (
" LastMimeType " , " application/x-txz " ) ) - > comment ( ) ,
0 , this , TQ_SLOT ( slotCompressAsDefault ( ) ) , actionCollection ( ) ) ;
}
actionMenu - > insert ( action ) ;
m_compAsMenu = new TDEActionMenu ( i18n ( " Compress As " ) , actionCollection ( ) , " arkcmpasmnu " ) ;
actionMenu - > insert ( m_compAsMenu ) ;
m_addToMenu = new TDEActionMenu ( i18n ( " Add To " ) , actionCollection ( ) , " arkaddtomnu " ) ;
if ( itemList . first ( ) - > url ( ) . isLocalFile ( ) )
actionMenu - > insert ( m_addToMenu ) ;
connect ( m_compAsMenu - > popupMenu ( ) , TQ_SIGNAL ( aboutToShow ( ) ) ,
this , TQ_SLOT ( slotPrepareCompAsMenu ( ) ) ) ;
connect ( m_addToMenu - > popupMenu ( ) , TQ_SIGNAL ( aboutToShow ( ) ) ,
this , TQ_SLOT ( slotPrepareAddToMenu ( ) ) ) ;
action = new TDEAction ( i18n ( " Add to Archive... " ) , 0 , this ,
TQ_SLOT ( slotAdd ( ) ) , actionCollection ( ) ) ;
actionMenu - > insert ( action ) ;
addAction ( actionMenu ) ;
}
if ( ! hasOther & & hasArchives )
{
if ( popupmenu - > protocolInfo ( ) . supportsWriting ( ) )
{
actionMenu = new TDEActionMenu ( i18n ( " Extract " ) , " ark " , actionCollection ( ) , " ark_extract_menu " ) ;
action = new TDEAction ( i18n ( " Extract Here " ) , 0 , this ,
TQ_SLOT ( slotExtractHere ( ) ) , actionCollection ( ) ) ;
actionMenu - > insert ( action ) ;
// stolen from arkwidget.cpp
if ( itemCount = = 1 )
{
TQString targetName = itemList . first ( ) - > name ( ) ;
stripExtension ( targetName ) ;
action = new TDEAction ( i18n ( " Extract to %1 " ) . arg ( targetName ) , 0 , this ,
TQ_SLOT ( slotExtractToSubfolders ( ) ) , actionCollection ( ) ) ;
}
else
{
action = new TDEAction ( i18n ( " Extract to Subfolders " ) , 0 , this ,
TQ_SLOT ( slotExtractToSubfolders ( ) ) , actionCollection ( ) ) ;
}
actionMenu - > insert ( action ) ;
action = new TDEAction ( i18n ( " Extract To... " ) , 0 , this ,
TQ_SLOT ( slotExtractTo ( ) ) , actionCollection ( ) ) ;
actionMenu - > insert ( action ) ;
addAction ( actionMenu ) ;
}
else
{
action = new TDEAction ( i18n ( " Extract To... " ) , " ark " , 0 , this , TQ_SLOT ( slotExtractTo ( ) ) , actionCollection ( ) , " ark_extract_menu " ) ;
addAction ( action ) ;
}
}
addSeparator ( ) ;
}
ArkMenu : : ~ ArkMenu ( )
{
delete m_conf ;
}
void ArkMenu : : slotPrepareCompAsMenu ( )
{
disconnect ( m_compAsMenu - > popupMenu ( ) , TQ_SIGNAL ( aboutToShow ( ) ) ,
this , TQ_SLOT ( slotPrepareCompAsMenu ( ) ) ) ;
TDEAction * action ;
m_compAsMapper = new TQSignalMapper ( this , " compAsMapper " ) ;
TQString ext ;
TQStringList newExt ;
unsigned int counter = 0 ;
TQCString actionName ;
TQStringList : : Iterator eit ;
TQStringList : : Iterator mit ;
mit = m_archiveMimeTypes . begin ( ) ;
for ( ; mit ! = m_archiveMimeTypes . end ( ) ; + + mit )
{
newExt = KMimeType : : mimeType ( * mit ) - > patterns ( ) ;
eit = newExt . begin ( ) ;
( * eit ) . remove ( ' * ' ) ;
if ( * eit = = " .tar.bz " ) // tbz mimetype, has tar.bz as first entry :}
* eit = " .tar.bz2 " ;
if ( m_urlList . count ( ) = = 1 )
{
action = new TDEAction ( m_name + ( * eit ) , 0 , m_compAsMapper ,
TQ_SLOT ( map ( ) ) , actionCollection ( ) ) ;
}
else
{
ext = KMimeType : : mimeType ( * mit ) - > comment ( ) ;
action = new TDEAction ( ext , 0 , m_compAsMapper ,
TQ_SLOT ( map ( ) ) , actionCollection ( ) ) ;
}
m_compAsMenu - > insert ( action ) ;
m_compAsMapper - > setMapping ( action , counter ) ;
+ + counter ;
+ + eit ;
while ( eit ! = newExt . end ( ) )
{
( * eit ) . remove ( ' * ' ) ;
+ + eit ;
+ + counter ;
}
m_extensionList + = newExt ;
}
connect ( m_compAsMapper , TQ_SIGNAL ( mapped ( int ) ) , TQ_SLOT ( slotCompressAs ( int ) ) ) ;
}
void ArkMenu : : slotPrepareAddToMenu ( )
{
disconnect ( m_addToMenu - > popupMenu ( ) , TQ_SIGNAL ( aboutToShow ( ) ) ,
this , TQ_SLOT ( slotPrepareAddToMenu ( ) ) ) ;
if ( m_extensionList . isEmpty ( ) ) // is filled in slotPrepareCompAsMenu
slotPrepareCompAsMenu ( ) ;
unsigned int counter = 0 ;
TDEAction * action ;
m_addToMapper = new TQSignalMapper ( this , " addToMapper " ) ;
TQCString actionName ;
TQStringList : : Iterator mit ;
KURL archive ;
TQDir dir ( m_urlList . first ( ) . directory ( ) ) ;
TQStringList entries = dir . entryList ( ) ;
TQStringList : : Iterator uit = entries . begin ( ) ;
for ( ; uit ! = entries . end ( ) ; + + uit )
{
for ( mit = m_extensionList . begin ( ) ; mit ! = m_extensionList . end ( ) ; + + mit )
if ( ( * uit ) . endsWith ( * mit ) )
{
action = new TDEAction ( * uit , 0 , m_addToMapper ,
TQ_SLOT ( map ( ) ) , actionCollection ( ) ) ;
m_addToMenu - > insert ( action ) ;
m_addToMapper - > setMapping ( action , counter ) ;
archive . setPath ( * uit ) ;
m_archiveList < < archive ;
counter + + ;
break ;
}
}
connect ( m_addToMapper , TQ_SIGNAL ( mapped ( int ) ) , TQ_SLOT ( slotAddTo ( int ) ) ) ;
}
void ArkMenu : : compMimeTypes ( )
{
unsigned int itemCount = m_urlList . count ( ) ;
bool havexz = false ;
if ( ! TDEStandardDirs : : findExe ( " xz " ) . isNull ( ) & & m_conf - > readBoolEntry ( " UseXz " , true ) )
{
havexz = true ;
m_archiveMimeTypes < < " application/x-xz " ;
}
bool havegz = false ;
if ( ! TDEStandardDirs : : findExe ( " gzip " ) . isNull ( ) & & m_conf - > readBoolEntry ( " UseGz " , true ) )
{
havegz = true ;
//.gz can only compress one file, not multiple
if ( itemCount = = 1 ) m_archiveMimeTypes < < " application/x-gzip " ;
}
bool havebz2 = false ;
if ( ! TDEStandardDirs : : findExe ( " bzip2 " ) . isNull ( ) & & m_conf - > readBoolEntry ( " UseBzip2 " , true ) )
{
havebz2 = true ;
//.bz2 can only compress one file, not multiple
if ( itemCount = = 1 ) m_archiveMimeTypes < < " application/x-bzip2 " ;
}
bool havelzop = false ;
if ( ! TDEStandardDirs : : findExe ( " lzop " ) . isNull ( ) & & m_conf - > readBoolEntry ( " UseLzop " , false ) )
{
havelzop = true ;
m_archiveMimeTypes < < " application/x-lzop " ;
}
bool havelzma = false ;
if ( ! TDEStandardDirs : : findExe ( " lzma " ) . isNull ( ) & & m_conf - > readBoolEntry ( " UseLzma " , false ) )
{
havelzma = true ;
m_archiveMimeTypes < < " application/x-lzma " ;
}
bool havelzip = false ;
if ( ! TDEStandardDirs : : findExe ( " lzip " ) . isNull ( ) & & m_conf - > readBoolEntry ( " UseLzip " , false ) )
{
havelzip = true ;
m_archiveMimeTypes < < " application/x-lzip " ;
}
if ( ! TDEStandardDirs : : findExe ( " tar " ) . isNull ( ) & & m_conf - > readBoolEntry ( " UseTar " , true ) )
{
if ( havexz )
m_archiveMimeTypes < < " application/x-txz " ;
if ( havegz )
m_archiveMimeTypes < < " application/x-tgz " ;
if ( havebz2 )
m_archiveMimeTypes < < " application/x-tbz " ;
if ( havelzop )
m_archiveMimeTypes < < " application/x-tzo " ;
if ( havelzma )
m_archiveMimeTypes < < " application/x-tlzma " ;
if ( havelzip )
m_archiveMimeTypes < < " application/x-tlz " ;
m_archiveMimeTypes < < " application/x-tar " ;
}
if ( ! TDEStandardDirs : : findExe ( " lha " ) . isNull ( ) & & m_conf - > readBoolEntry ( " UseLha " , false ) )
m_archiveMimeTypes < < " application/x-lha " ;
if ( ! TDEStandardDirs : : findExe ( " zip " ) . isNull ( ) & & m_conf - > readBoolEntry ( " UseZip " , true ) )
{
m_archiveMimeTypes < < " application/x-zip " ;
if ( m_conf - > readBoolEntry ( " UseJar " , true ) )
m_archiveMimeTypes < < " application/x-jar " ;
}
if ( ! TDEStandardDirs : : findExe ( " rar " ) . isNull ( ) & & m_conf - > readBoolEntry ( " UseRar " , true ) )
m_archiveMimeTypes < < " application/x-rar " ;
if ( ! TDEStandardDirs : : findExe ( " 7z " ) . isNull ( ) & & m_conf - > readBoolEntry ( " Use7z " , true ) )
m_archiveMimeTypes < < " application/x-7z " ;
else if ( ! TDEStandardDirs : : findExe ( " 7za " ) . isNull ( ) & & m_conf - > readBoolEntry ( " Use7za " , false ) )
m_archiveMimeTypes < < " application/x-7z " ;
else if ( ! TDEStandardDirs : : findExe ( " 7zr " ) . isNull ( ) & & m_conf - > readBoolEntry ( " Use7zr " , false ) )
m_archiveMimeTypes < < " application/x-7z " ;
if ( ! TDEStandardDirs : : findExe ( " zoo " ) . isNull ( ) & & m_conf - > readBoolEntry ( " UseZoo " , false ) )
m_archiveMimeTypes < < " application/x-zoo " ;
if ( ! TDEStandardDirs : : findExe ( " compress " ) . isNull ( ) & & m_conf - > readBoolEntry ( " UseCompress " , false ) )
m_archiveMimeTypes < < " application/x-compress " ;
if ( ! TDEStandardDirs : : findExe ( " bzip " ) . isNull ( ) & & m_conf - > readBoolEntry ( " UseBzip " , false ) )
m_archiveMimeTypes < < " application/x-bzip " ;
if ( ! TDEStandardDirs : : findExe ( " ar " ) . isNull ( ) & & m_conf - > readBoolEntry ( " UseAr " , false ) )
m_archiveMimeTypes < < " application/x-archive " ;
}
void ArkMenu : : extMimeTypes ( )
{
bool havexz = false ;
if ( ! TDEStandardDirs : : findExe ( " xz " ) . isNull ( ) )
{
havexz = true ;
m_extractMimeTypes < < " application/x-xz " ;
}
bool havegz = false ;
if ( ! TDEStandardDirs : : findExe ( " gunzip " ) . isNull ( ) )
{
havegz = true ;
m_extractMimeTypes < < " application/x-gzip " ;
m_extractMimeTypes < < " application/x-gzpostscript " ;
}
bool havebz2 = false ;
if ( ! TDEStandardDirs : : findExe ( " bunzip2 " ) . isNull ( ) )
{
havebz2 = true ;
m_extractMimeTypes < < " application/x-bzip2 " ;
}
bool havelzop = false ;
if ( ! TDEStandardDirs : : findExe ( " lzop " ) . isNull ( ) )
{
havelzop = true ;
m_extractMimeTypes < < " application/x-lzop " ;
}
bool havelzma = false ;
if ( ! TDEStandardDirs : : findExe ( " lzma " ) . isNull ( ) )
{
havelzma = true ;
m_extractMimeTypes < < " application/x-lzma " ;
}
bool havelzip = false ;
if ( ! TDEStandardDirs : : findExe ( " lzip " ) . isNull ( ) )
{
havelzip = true ;
m_extractMimeTypes < < " application/x-lzip " ;
}
if ( ! TDEStandardDirs : : findExe ( " tar " ) . isNull ( ) )
{
if ( havexz )
m_extractMimeTypes < < " application/x-txz " ;
if ( havegz )
m_extractMimeTypes < < " application/x-tgz " ;
if ( havebz2 )
m_extractMimeTypes < < " application/x-tbz " ;
if ( havelzop )
m_extractMimeTypes < < " application/x-tzo " ;
if ( havelzma )
m_extractMimeTypes < < " application/x-tlzma " ;
if ( havelzip )
m_extractMimeTypes < < " application/x-tlz " ;
m_extractMimeTypes < < " application/x-tar " ;
}
if ( ! TDEStandardDirs : : findExe ( " lha " ) . isNull ( ) )
m_extractMimeTypes < < " application/x-lha " ;
if ( ! TDEStandardDirs : : findExe ( " zip " ) . isNull ( ) )
m_extractMimeTypes < < " application/x-zip " < < " application/x-jar " ;
if ( ! TDEStandardDirs : : findExe ( " unrar " ) . isNull ( ) )
m_extractMimeTypes < < " application/x-rar " ;
if ( ! TDEStandardDirs : : findExe ( " 7z " ) . isNull ( ) | | ! TDEStandardDirs : : findExe ( " 7za " ) . isNull ( ) | | ! TDEStandardDirs : : findExe ( " 7zr " ) . isNull ( ) )
m_extractMimeTypes < < " application/x-7z " ;
if ( ! TDEStandardDirs : : findExe ( " zoo " ) . isNull ( ) )
m_extractMimeTypes < < " application/x-zoo " ;
if ( ! TDEStandardDirs : : findExe ( " uncompress " ) . isNull ( ) )
m_extractMimeTypes < < " application/x-compress " ;
if ( ! TDEStandardDirs : : findExe ( " bunzip " ) . isNull ( ) )
m_extractMimeTypes < < " application/x-bzip " ;
if ( ! TDEStandardDirs : : findExe ( " ar " ) . isNull ( ) )
m_extractMimeTypes < < " application/x-archive " ;
}
void ArkMenu : : stripExtension ( TQString & name )
{
TQStringList patternList = KMimeType : : findByPath ( name ) - > patterns ( ) ;
TQStringList : : Iterator it = patternList . begin ( ) ;
TQString ext ;
for ( ; it ! = patternList . end ( ) ; + + it )
{
ext = ( * it ) . remove ( ' * ' ) ;
if ( name . endsWith ( ext ) )
{
name = name . left ( name . findRev ( ext ) ) + ' / ' ;
break ;
}
}
}
void ArkMenu : : slotCompressAs ( int pos )
{
TQCString name ;
TQString extension , mimeType ;
KURL target ;
TQStringList filelist ( m_urlStringList ) ;
//if KMimeType returns .ZIP, .7Z or .RAR. convert them to lowercase
if ( m_extensionList [ pos ] . contains ( " .ZIP " ) )
m_extensionList [ pos ] = " .zip " ;
if ( m_extensionList [ pos ] . contains ( " .RAR " ) )
m_extensionList [ pos ] = " .rar " ;
if ( m_extensionList [ pos ] . contains ( " .7Z " ) )
m_extensionList [ pos ] = " .7z " ;
if ( filelist . count ( ) = = 1 )
target = filelist . first ( ) + m_extensionList [ pos ] ;
else
{
target = m_dir + i18n ( " Archive " ) + m_extensionList [ pos ] ;
int i = 1 ;
while ( TDEIO : : NetAccess : : exists ( target , true , 0 ) )
{
target = m_dir + i18n ( " Archive %1 " ) . arg ( i ) + m_extensionList [ pos ] ;
i + + ;
}
}
compressAs ( filelist , target ) ;
extension = m_extensionList [ pos ] ;
m_conf - > setGroup ( " ArkPlugin " ) ;
m_conf - > writeEntry ( " LastExtension " , extension ) ;
TQStringList extensions ;
TQStringList : : Iterator eit ;
TQStringList : : Iterator mit = m_archiveMimeTypes . begin ( ) ;
bool done = false ;
for ( ; mit ! = m_archiveMimeTypes . end ( ) & & ! done ; + + mit )
{
extensions = KMimeType : : mimeType ( * mit ) - > patterns ( ) ;
eit = extensions . begin ( ) ;
for ( ; eit ! = extensions . end ( ) ; + + eit )
{
( * eit ) . remove ( ' * ' ) ;
if ( ( * eit ) = = extension )
{
m_conf - > writeEntry ( " LastMimeType " , * mit ) ;
done = true ;
break ;
}
}
}
m_conf - > sync ( ) ;
}
void ArkMenu : : slotCompressAsDefault ( )
{
KURL name ;
if ( m_urlStringList . count ( ) = = 1 )
name = m_urlStringList . first ( ) + m_ext ;
else
{
name = m_dir + i18n ( " Archive " ) + m_ext ;
int i = 1 ;
while ( TDEIO : : NetAccess : : exists ( name , true , 0 ) )
{
name = m_dir + i18n ( " Archive %1 " ) . arg ( i ) + m_ext ;
i + + ;
}
}
compressAs ( m_urlStringList , name ) ;
}
// make work for URLs
void ArkMenu : : compressAs ( const TQStringList & name , const KURL & compressed )
{
TQStringList args ;
args < < " --add-to " ;
args + = name ;
args < < compressed . url ( ) ;
kapp - > tdeinitExec ( " ark " , args ) ;
}
void ArkMenu : : slotAddTo ( int pos )
{
TQStringList args ( m_urlStringList ) ;
args . prepend ( " --add-to " ) ;
KURL archive ( m_urlStringList . first ( ) ) ;
archive . setPath ( archive . directory ( false ) ) ;
archive . setFileName ( m_archiveList [ pos ] . fileName ( ) ) ;
args < < archive . url ( ) ;
kapp - > tdeinitExec ( " ark " , args ) ;
}
void ArkMenu : : slotAdd ( )
{
TQStringList args ( m_urlStringList ) ;
args . prepend ( " --add " ) ;
kapp - > tdeinitExec ( " ark " , args ) ;
}
void ArkMenu : : slotExtractHere ( )
{
for ( TQValueList < KURL > : : ConstIterator it = m_urlList . constBegin ( ) ;
it ! = m_urlList . constEnd ( ) ;
+ + it )
{
TQStringList args ;
KURL targetDirectory = ( * it ) . url ( ) ;
targetDirectory . setPath ( targetDirectory . directory ( ) ) ;
args < < " --extract-to " < < targetDirectory . url ( ) < < ( * it ) . url ( ) ;
kapp - > tdeinitExec ( " ark " , args ) ;
}
}
void ArkMenu : : slotExtractToSubfolders ( )
{
for ( TQStringList : : ConstIterator it = m_urlStringList . constBegin ( ) ;
it ! = m_urlStringList . constEnd ( ) ;
+ + it )
{
KURL targetDir ;
TQString dirName ;
TQStringList args ;
targetDir = * it ;
dirName = targetDir . path ( ) ;
stripExtension ( dirName ) ;
targetDir . setPath ( dirName ) ;
args < < " --extract-to " < < targetDir . url ( ) < < * it ;
kapp - > tdeinitExec ( " ark " , args ) ;
}
}
void ArkMenu : : slotExtractTo ( )
{
for ( TQStringList : : ConstIterator it = m_urlStringList . constBegin ( ) ;
it ! = m_urlStringList . constEnd ( ) ;
+ + it )
{
TQStringList args ;
args < < " --extract " < < * it ;
kapp - > tdeinitExec ( " ark " , args ) ;
}
}
# include "arkplugin.moc"