1 /*************************************************************************
9 * The Contents of this file are made available subject to the terms of
10 * either of the following licenses
12 * - GNU Lesser General Public License Version 2.1
13 * - Sun Industry Standards Source License Version 1.1
15 * Sun Microsystems Inc., October, 2000
17 * GNU Lesser General Public License Version 2.1
18 * =============================================
19 * Copyright 2000 by Sun Microsystems, Inc.
20 * 901 San Antonio Road, Palo Alto, CA 94303, USA
22 * This library is free software; you can redistribute it and/or
23 * modify it under the terms of the GNU Lesser General Public
24 * License version 2.1, as published by the Free Software Foundation.
26 * This library is distributed in the hope that it will be useful,
27 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
29 * Lesser General Public License for more details.
31 * You should have received a copy of the GNU Lesser General Public
32 * License along with this library; if not, write to the Free Software
33 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
37 * Sun Industry Standards Source License Version 1.1
38 * =================================================
39 * The contents of this file are subject to the Sun Industry Standards
40 * Source License Version 1.1 (the "License"); You may not use this file
41 * except in compliance with the License. You may obtain a copy of the
42 * License at http://www.openoffice.org/license.html.
44 * Software provided under this License is provided on an "AS IS" basis,
45 * WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
46 * WITHOUT LIMITATION, WARRANTIES THAT THE SOFTWARE IS FREE OF DEFECTS,
47 * MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE, OR NON-INFRINGING.
48 * See the License for the specific provisions governing your rights and
49 * obligations concerning the Software.
51 * The Initial Developer of the Original Code is: Sun Microsystems, Inc.
53 * Copyright: 2000 by Sun Microsystems, Inc.
55 * All Rights Reserved.
57 * Contributor(s): Jan Holesovsky <kendy@openoffice.org>
60 ************************************************************************/
62 #include <kdecommandthread.hxx>
63 #include <kdefilepicker.hxx>
65 #include <qcheckbox.h>
66 #include <qcombobox.h>
71 #include <qobjectlist.h>
72 #include <qpushbutton.h>
80 #include <kdeversion.h>
81 #include <kdiroperator.h>
82 #include <kfiledialog.h>
83 #include <kfilefiltercombo.h>
84 #include <kio/netaccess.h>
86 #include <kmessagebox.h>
87 #include <ktempfile.h>
92 //////////////////////////////////////////////////////////////////////////
94 //////////////////////////////////////////////////////////////////////////
96 FileDialog::FileDialog( const QString
&startDir
, const QString
&filter
,
97 QWidget
*parent
, const char *name
)
98 : KFileDialog( startDir
, filter
, parent
, name
, true, m_pCustomWidget
= new QVBox() ),
99 m_pCombosAndButtons( new QHBox( m_pCustomWidget
) ),
100 m_pLabels( new QVBox( m_pCombosAndButtons
) ),
101 m_pComboBoxes( new QVBox( m_pCombosAndButtons
) ),
102 m_pPushButtons( new QVBox( m_pCombosAndButtons
) ),
103 m_pCheckBoxes( new QGrid( 2, m_pCustomWidget
) ),
105 m_bIsExecuting( false ),
106 m_bCanNotifySelection( true )
108 connect( this, SIGNAL( fileHighlighted( const QString
& ) ),
109 this, SLOT( fileHighlightedCommand( const QString
& ) ) );
111 connect( this, SIGNAL( selectionChanged() ),
112 this, SLOT( selectionChangedCommand() ) );
114 m_pCustomWidget
->setSpacing( KDialog::spacingHint() );
115 m_pCombosAndButtons
->setSpacing( KDialog::spacingHint() );
117 updateCustomWidgetLayout();
120 FileDialog::~FileDialog()
124 void FileDialog::resizeEvent( QResizeEvent
*pEvent
)
126 KFileDialog::resizeEvent( pEvent
);
128 updateCustomWidgetLayout();
131 void FileDialog::showEvent( QShowEvent
*pEvent
)
133 KFileDialog::showEvent( pEvent
);
135 updateCustomWidgetLayout();
138 void FileDialog::updateCustomWidgetLayout()
140 QPoint qReferencePoint
= filterWidget
->mapTo( this, QPoint( 0, 0 ) );
141 QPoint qCustomPoint
= m_pCustomWidget
->mapTo( this, QPoint( 0, 0 ) );
143 int nLeft
= qReferencePoint
.x() - qCustomPoint
.x();
144 int nRight
= m_pCustomWidget
->width() - filterWidget
->width() - nLeft
;
146 nLeft
-= KDialog::spacingHint();
147 nRight
-= KDialog::spacingHint();
148 m_pLabels
->setFixedWidth( ( nLeft
> 0 )? nLeft
: 80 );
149 // FIXME The following call sets the width of m_pPushButtons all right,
150 // but it also increases the width of m_pComboBoxes rapidly. Can we do
151 // anything about it?
152 m_pPushButtons
->setFixedWidth( ( nRight
> 0 )? nRight
: 100 );
155 void FileDialog::customEvent( QCustomEvent
*pEvent
)
157 if ( pEvent
&& pEvent
->type() == CommandEvent::TypeId
)
159 CommandEvent
*pCommandEvent
= static_cast< CommandEvent
* >( pEvent
);
160 QStringList
*pStringList
= pCommandEvent
->stringList();
164 nListSize
= pStringList
->size();
166 switch ( pCommandEvent
->command() )
168 case CommandEvent::AppendControl
:
169 if ( nListSize
>= 3 )
171 appendControl( (*pStringList
)[0], (*pStringList
)[1], (*pStringList
)[2] );
174 case CommandEvent::EnableControl
:
175 if ( nListSize
>= 2 )
177 enableControl( (*pStringList
)[0], (*pStringList
)[1] );
180 case CommandEvent::GetValue
:
181 if ( nListSize
>= 2 )
183 getValue( (*pStringList
)[0], (*pStringList
)[1] );
186 case CommandEvent::SetValue
:
187 if ( nListSize
>= 2 )
189 QStringList qStringList
= (*pStringList
);
190 qStringList
.pop_front();
191 qStringList
.pop_front();
193 setValue( (*pStringList
)[0], (*pStringList
)[1], qStringList
);
196 case CommandEvent::AppendFilter
:
197 if ( nListSize
>= 2 )
199 appendFilter( (*pStringList
)[0], (*pStringList
)[1] );
201 // update the filters widget
202 setFilter( filters() );
205 case CommandEvent::AppendFilterGroup
:
206 if ( nListSize
>= 1 )
208 QStringList::const_iterator it
= pStringList
->begin();
209 ++it
; // We ignore the filter group name
211 while ( it
!= pStringList
->end() )
213 QString qTitle
= *it
;
215 if ( it
!= pStringList
->end() )
217 appendFilter( qTitle
, (*it
) );
222 // update the filters widget
223 setFilter( filters() );
226 case CommandEvent::GetCurrentFilter
:
228 QString qCurrentFilter
= filterWidget
->currentText();
229 sendCommand( "currentFilter " + escapeString( qCurrentFilter
) );
232 case CommandEvent::SetCurrentFilter
:
233 if ( nListSize
>= 1 )
235 static_cast< FileFilterComboHack
* >( filterWidget
)->setCurrentFilter( pStringList
->front() );
238 case CommandEvent::GetDirectory
:
240 QString qDirectory
= baseURL().url();
241 if ( qDirectory
.startsWith( "file:/" ) && qDirectory
.mid( 6, 1 ) != "/" )
242 qDirectory
.replace( "file:/", "file:///" );
243 sendCommand( "currentDirectory " + escapeString( qDirectory
) );
246 case CommandEvent::SetDirectory
:
247 if ( nListSize
>= 1 )
249 setURL( pStringList
->front() );
252 case CommandEvent::GetFiles
:
255 qString
.reserve( 1024 );
257 qString
.append( "files" );
259 if ( result() == QDialog::Accepted
)
261 KURL::List
qList( selectedURLs() );
262 for ( KURL::List::const_iterator it
= qList
.begin(); it
!= qList
.end(); ++it
)
263 appendURL( qString
, (*it
) );
267 // we have to return the selected files anyway
268 const KFileItemList
*pItems
= ops
->selectedItems();
269 for ( KFileItemListIterator
it( *pItems
); it
.current(); ++it
)
270 appendURL( qString
, (*it
)->url() );
273 sendCommand( qString
);
274 setCanNotifySelection( true );
277 case CommandEvent::SetTitle
:
278 if ( nListSize
>= 1 )
280 setCaption( pStringList
->front() );
283 case CommandEvent::SetType
:
284 if ( nListSize
>= 1 )
286 QString
qType( pStringList
->front() );
287 if ( qType
== "open" )
290 setCaption( i18n( "Open" ) );
292 else if ( qType
== "save" )
295 setCaption( i18n( "Save As" ) );
299 case CommandEvent::SetDefaultName
:
300 if ( nListSize
>= 1 )
302 setKeepLocation( true );
303 setSelection( pStringList
->front() );
306 case CommandEvent::SetMultiSelection
:
307 if ( nListSize
>= 1 )
309 if ( pStringList
->front() == "true" )
310 setMode( KFile::Files
);
312 setMode( KFile::File
);
315 case CommandEvent::Exec
:
317 filterWidget
->setEditable( false );
318 setIsExecuting( true );
319 bool bCanExit
= false;
321 setCanNotifySelection( true );
324 KURL qLocalSelectedURL
= mostLocalURL( selectedURL() );
325 QString
qProtocol( qLocalSelectedURL
.protocol() );
327 if ( isSave() && result() == QDialog::Accepted
)
329 if ( qProtocol
== "file" )
331 QString
qFileName( addExtension( qLocalSelectedURL
.path() ) );
333 !QFile::exists( qFileName
) ||
334 ( KMessageBox::warningYesNo( 0,
335 i18n( "A file named \"%1\" already exists. "
336 "Are you sure you want to overwrite it?" ).arg( qFileName
),
337 i18n( "Overwrite File?" ),
338 i18n( "Overwrite" ), KStdGuiItem::cancel() ) == KMessageBox::Yes
);
340 else if ( !isSupportedProtocol( qProtocol
) )
342 KMessageBox::sorry( 0,
343 i18n( "Saving using protocol \"%1\" is not supported." ).arg( qProtocol
) );
349 else if ( !isSave() && result() == QDialog::Accepted
&& !isSupportedProtocol( qProtocol
) )
351 KMessageBox::information( 0,
352 i18n( "Protocol \"%1\" is supported only partially. "
353 "Local copy of the file will be created." ).arg( qProtocol
) );
358 } while ( !bCanExit
);
359 setIsExecuting( false );
361 if ( result() == QDialog::Accepted
)
362 sendCommand( "accept" );
364 sendCommand( "reject" );
371 // FIXME Some cleanup of pEvent? delete something, etc.?
375 void FileDialog::appendControl( const QString
&rId
, const QString
&rType
, const QString
&rTitle
)
377 QString
qLabel( rTitle
);
378 qLabel
.replace( '~', '&' );
380 if ( rType
== "checkbox" )
382 QCheckBox
*pCheckBox
= new QCheckBox( qLabel
, m_pCheckBoxes
, rId
.utf8() );
384 pCheckBox
->setEnabled( true );
385 pCheckBox
->setChecked( false );
387 else if ( rType
== "listbox" )
389 QLabel
*pComboLabel
= new QLabel( qLabel
, m_pLabels
);
390 QComboBox
*pComboBox
= new QComboBox( m_pComboBoxes
, rId
.utf8() );
392 pComboLabel
->setBuddy( pComboBox
);
393 pComboBox
->setEnabled( true );
395 else if ( rType
== "pushbutton" )
397 QPushButton
*pPushButton
= new QPushButton( qLabel
, m_pPushButtons
, rId
.utf8() );
398 pPushButton
->setEnabled( true );
402 QWidget
* FileDialog::findControl( const QString
&rId
) const
404 QObjectList
*pList
= m_pCustomWidget
->queryList();
405 QCString
qName( rId
.utf8() );
406 QObjectList::const_iterator it
= pList
->begin();
408 for ( ; it
!= pList
->end() && qName
!= (*it
)->name(); ++it
)
411 QWidget
*pWidget
= NULL
;
412 if ( it
!= pList
->end() )
413 pWidget
= static_cast< QWidget
* >( *it
);
420 void FileDialog::enableControl( const QString
&rId
, const QString
&rValue
)
422 QWidget
*pWidget
= findControl( rId
);
425 pWidget
->setEnabled( rValue
.lower() == "true" );
428 void FileDialog::getValue( const QString
&rId
, const QString
&rAction
)
430 QWidget
*pWidget
= findControl( rId
);
432 qString
.reserve( 1024 );
433 qString
.append( "value" );
437 QCString qClassName
= pWidget
->className();
438 if ( qClassName
== "QCheckBox" )
440 QCheckBox
*pCheckBox
= static_cast< QCheckBox
* >( pWidget
);
442 if ( pCheckBox
->isChecked() )
443 qString
.append( " bool true" );
445 qString
.append( " bool false" );
447 else if ( qClassName
== "QComboBox" )
449 QComboBox
*pComboBox
= static_cast< QComboBox
* >( pWidget
);
450 if ( rAction
== "getItems" )
452 qString
.append( " stringList" );
453 for ( int nIdx
= 0; nIdx
< pComboBox
->count(); ++nIdx
)
455 qString
.append( ' ' );
456 appendEscaped( qString
, pComboBox
->text( nIdx
) );
459 else if ( rAction
== "getSelectedItem" )
461 qString
.append( " string " );
462 appendEscaped( qString
, pComboBox
->currentText() );
464 else if ( rAction
== "getSelectedItemIndex" )
466 qString
.append( " int " );
467 qString
.append( QString().setNum( pComboBox
->currentItem() ) );
474 sendCommand( qString
);
477 void FileDialog::setValue( const QString
&rId
, const QString
&rAction
, const QStringList
&rValue
)
479 QWidget
*pWidget
= findControl( rId
);
483 QCString qClassName
= pWidget
->className();
484 if ( qClassName
== "QCheckBox" )
486 QCheckBox
*pCheckBox
= static_cast< QCheckBox
* >( pWidget
);
488 bool bValue
= ( !rValue
.isEmpty() ) && ( rValue
.front().lower() == "true" );
489 pCheckBox
->setChecked( bValue
);
491 else if ( qClassName
== "QComboBox" )
493 QComboBox
*pComboBox
= static_cast< QComboBox
* >( pWidget
);
494 if ( rAction
== "addItem" )
496 if ( !rValue
.isEmpty() )
497 pComboBox
->insertItem( rValue
.front() );
499 else if ( rAction
== "addItems" )
501 pComboBox
->insertStringList( rValue
);
503 else if ( rAction
== "deleteItem" )
505 if ( !rValue
.isEmpty() )
506 pComboBox
->removeItem( rValue
.front().toInt() );
508 else if ( rAction
== "deleteItems" )
512 else if ( rAction
== "setSelectedItem" )
514 if ( !rValue
.isEmpty() )
515 pComboBox
->setCurrentItem( rValue
.front().toInt() );
517 // FIXME setHelpURL is ignored
523 void FileDialog::appendFilter( const QString
&rTitle
, const QString
&rFilter
)
525 // Filters are separated by ';'
526 QString
qFilter( rFilter
);
527 qFilter
.replace( QChar( ';' ), QChar( ' ' ) ).replace( "*.*", "*" );
529 // Workaround for too wide <All formats> (*.bmp;...) entry
530 QString
qTitle( rTitle
);
531 qTitle
.replace( QRegExp( "^<([^>]*)> \\(.*" ), "<\\1>" );
533 m_aFilters
.push_back( qMakePair( qTitle
, qFilter
) );
536 QString
FileDialog::filters() const
538 QString qString
, qTmp
;
539 bool bFirstFilter
= true;
541 for ( FilterList::const_iterator it
= m_aFilters
.begin(); it
!= m_aFilters
.end(); ++it
)
544 bFirstFilter
= false;
546 qString
.append( '\n' );
548 qString
.append( (*it
).second
);
549 qString
.append( '|' );
552 qString
.append( qTmp
.replace( '/', "\\/" ) );
558 QString
FileDialog::addExtension( const QString
&rFileName
) const
565 QWidget
*pExtensionWidget
= findControl( "100" ); // CHECKBOX_AUTOEXTENSION
566 QCheckBox
*pExtensionCB
= pExtensionWidget
? static_cast< QCheckBox
* >( pExtensionWidget
->qt_cast( "QCheckBox" ) ): NULL
;
567 if ( pExtensionCB
&& pExtensionCB
->isChecked() )
569 // FIXME: qFilter can be a MIME; we ignore it now...
570 QStringList qFilterList
= QStringList::split( " ", currentFilter() );
571 for ( QStringList::const_iterator it
= qFilterList
.begin();
572 qExtension
.isEmpty() && it
!= qFilterList
.end();
575 int nUnwanted
= (*it
).findRev( '*' );
577 nUnwanted
= (*it
).findRev( '?' );
579 nUnwanted
= ::std::max( nUnwanted
, (*it
).find( '?', nUnwanted
) );
581 int nIdx
= (*it
).find( '.', ::std::max( nUnwanted
, 0 ) );
583 qExtension
= (*it
).mid( nIdx
).lower();
587 if ( qExtension
.isEmpty() || qExtension
== "." || rFileName
.endsWith( qExtension
) )
590 return rFileName
+ qExtension
;
593 bool FileDialog::isSupportedProtocol( const QString
&rProtocol
) const
595 // TODO Get this information directly from OOo
596 const char * pOOoProtocols
[] = { "", "smb", "ftp", "http", "file", "mailto",
597 "vnd.sun.star.webdav", "news", "private", "vnd.sun.star.help",
598 "https", "slot", "macro", "javascript", "imap", "pop3", "data",
599 "cid", "out", "vnd.sun.star.wfs", "vnd.sun.star.hier", "vim",
600 ".uno", ".component", "vnd.sun.star.pkg", "ldap", "db",
601 "vnd.sun.star.cmd", "vnd.sun.star.script", "vnd.sun.star.odma",
605 for ( const char **pIndex
= pOOoProtocols
; *pIndex
!= NULL
; ++pIndex
)
607 if ( rProtocol
== *pIndex
)
611 // TODO gnome-vfs bits here
616 KURL
FileDialog::mostLocalURL( const KURL
&rURL
) const
618 #if KDE_IS_VERSION(3,5,0)
619 KURL
qMostLocalURL( KIO::NetAccess::mostLocalURL( rURL
, const_cast<FileDialog
*>( this ) ) );
620 if ( qMostLocalURL
.isLocalFile() )
621 return qMostLocalURL
;
624 // Terrible hack to get even non-existing media:// files right
625 qMostLocalURL
.cd( ".." );
626 KURL
qMostLocalPath( KIO::NetAccess::mostLocalURL( qMostLocalURL
, const_cast<FileDialog
*>( this ) ) );
627 if ( qMostLocalPath
.isLocalFile() )
629 qMostLocalPath
.addPath( rURL
.fileName() );
630 return qMostLocalPath
;
638 QString
FileDialog::localCopy( const QString
&rFileName
) const
640 // 106 == MIB enum for UTF-8
641 KURL qLocalURL
= mostLocalURL( KURL( rFileName
, 106 ) );
642 if ( qLocalURL
.isLocalFile() )
643 return qLocalURL
.url();
645 int nExtensionPos
= rFileName
.findRev( '/' );
646 if ( nExtensionPos
>= 0 )
647 nExtensionPos
= rFileName
.find( '.', nExtensionPos
);
649 nExtensionPos
= rFileName
.find( '.' );
651 KTempFile
qTempFile( QString::null
, ( nExtensionPos
< 0 )? QString(): rFileName
.mid( nExtensionPos
) );
653 qDestURL
.setPath( qTempFile
.name() );
655 if ( !KIO::NetAccess::file_copy( rFileName
, qDestURL
, 0600, true, false, NULL
) )
657 KMessageBox::error( 0, KIO::NetAccess::lastErrorString() );
658 return QString::null
;
661 return qDestURL
.url();
664 void FileDialog::fileHighlightedCommand( const QString
& )
666 if ( canNotifySelection() )
668 sendCommand( "fileSelectionChanged" );
669 setCanNotifySelection( false );
673 void FileDialog::selectionChangedCommand()
675 if ( canNotifySelection() )
677 sendCommand( "fileSelectionChanged" );
678 setCanNotifySelection( false );
682 void FileDialog::sendCommand( const QString
&rCommand
)
684 #if OSL_DEBUG_LEVEL > 0
685 ::std::cerr
<< "kdefilepicker sent: " << rCommand
.latin1() << ::std::endl
;
688 //m_aOutputStream << rCommand << endl;
689 ::std::cout
<< rCommand
.utf8() << ::std::endl
;
692 void FileDialog::appendURL( QString
&rBuffer
, const KURL
&rURL
)
694 // From Martin Kretzschmar:
695 // file:///path/to/test%E0.odt is not a valid URL from OOo's point of
696 // view. (?Most modern parts of?) OOo assume(s) that the URL contains only
697 // ASCII characters (which test%E0.odt does) and is UTF-8 after unescaping
698 // (which file:///path/test%E0.odt is not).
699 // Cf. the comment in sal/inc/osl/file.h.
700 // 106 == MIB enum for UTF-8
701 QString qUrlStr
= addExtension( rURL
.url( 0, 106 ) );
703 if ( !isExecuting() && !isSupportedProtocol( rURL
.protocol() ) )
704 qUrlStr
= localCopy( qUrlStr
);
706 if ( qUrlStr
.startsWith( "file:/" ) && qUrlStr
.mid( 6, 1 ) != "/" )
707 qUrlStr
.replace( "file:/", "file:///" );
709 rBuffer
.append( " " );
710 if ( !qUrlStr
.isEmpty() )
711 appendEscaped( rBuffer
, qUrlStr
);
714 void FileDialog::appendEscaped( QString
&rBuffer
, const QString
&rString
)
716 const QChar
*pUnicode
= rString
.unicode();
717 const QChar
*pEnd
= pUnicode
+ rString
.length();
719 rBuffer
.append( '"' );
720 for ( ; pUnicode
!= pEnd
; ++pUnicode
)
722 if ( *pUnicode
== '\\' )
723 rBuffer
.append( "\\\\" );
724 else if ( *pUnicode
== '"' )
725 rBuffer
.append( "\\\"" );
726 else if ( *pUnicode
== '\n' )
727 rBuffer
.append( "\\\n" );
729 rBuffer
.append( *pUnicode
);
731 rBuffer
.append( '"' );
734 QString
FileDialog::escapeString( const QString
&rString
)
737 qString
.reserve( 2*rString
.length() + 2 ); // every char escaped + quotes
739 appendEscaped( qString
, rString
);
745 void FileFilterComboHack::setCurrentFilter( const QString
& filter
)
747 setCurrentText( filter
);
750 // Workaround for 'Filter name (*.blah)' vs. 'Filter name'
751 if ( currentText() != text( currentItem() ) )
754 for ( ; nItem
< count() && !text( nItem
).startsWith( filter
); ++nItem
);
756 if ( nItem
< count() )
757 setCurrentItem( nItem
);