Update ooo320-m1
[ooovba.git] / fpicker / source / unx / kde / kdefilepicker.cxx
blob62e453bbf2419873f5a4ae989d35fbdde91850c7
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,
34 * MA 02111-1307 USA
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>
67 #include <qgrid.h>
68 #include <qhbox.h>
69 #include <qlabel.h>
70 #include <qlayout.h>
71 #include <qobjectlist.h>
72 #include <qpushbutton.h>
73 #include <qregexp.h>
74 #include <qvbox.h>
76 #ifdef QT_NO_EMIT
77 #define emit
78 #endif
80 #include <kdeversion.h>
81 #include <kdiroperator.h>
82 #include <kfiledialog.h>
83 #include <kfilefiltercombo.h>
84 #include <kio/netaccess.h>
85 #include <klocale.h>
86 #include <kmessagebox.h>
87 #include <ktempfile.h>
89 #include <algorithm>
90 #include <iostream>
92 //////////////////////////////////////////////////////////////////////////
93 // FileDialog
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 ) ),
104 m_bIsSave( false ),
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();
162 int nListSize = -1;
163 if ( pStringList )
164 nListSize = pStringList->size();
166 switch ( pCommandEvent->command() )
168 case CommandEvent::AppendControl:
169 if ( nListSize >= 3 )
171 appendControl( (*pStringList)[0], (*pStringList)[1], (*pStringList)[2] );
173 break;
174 case CommandEvent::EnableControl:
175 if ( nListSize >= 2 )
177 enableControl( (*pStringList)[0], (*pStringList)[1] );
179 break;
180 case CommandEvent::GetValue:
181 if ( nListSize >= 2 )
183 getValue( (*pStringList)[0], (*pStringList)[1] );
185 break;
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 );
195 break;
196 case CommandEvent::AppendFilter:
197 if ( nListSize >= 2 )
199 appendFilter( (*pStringList)[0], (*pStringList)[1] );
201 // update the filters widget
202 setFilter( filters() );
204 break;
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;
214 ++it;
215 if ( it != pStringList->end() )
217 appendFilter( qTitle, (*it) );
218 ++it;
222 // update the filters widget
223 setFilter( filters() );
225 break;
226 case CommandEvent::GetCurrentFilter:
228 QString qCurrentFilter = filterWidget->currentText();
229 sendCommand( "currentFilter " + escapeString( qCurrentFilter ) );
231 break;
232 case CommandEvent::SetCurrentFilter:
233 if ( nListSize >= 1 )
235 static_cast< FileFilterComboHack* >( filterWidget )->setCurrentFilter( pStringList->front() );
237 break;
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 ) );
245 break;
246 case CommandEvent::SetDirectory:
247 if ( nListSize >= 1 )
249 setURL( pStringList->front() );
251 break;
252 case CommandEvent::GetFiles:
254 QString qString;
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) );
265 else
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 );
276 break;
277 case CommandEvent::SetTitle:
278 if ( nListSize >= 1 )
280 setCaption( pStringList->front() );
282 break;
283 case CommandEvent::SetType:
284 if ( nListSize >= 1 )
286 QString qType( pStringList->front() );
287 if ( qType == "open" )
289 setIsSave( false );
290 setCaption( i18n( "Open" ) );
292 else if ( qType == "save" )
294 setIsSave( true );
295 setCaption( i18n( "Save As" ) );
298 break;
299 case CommandEvent::SetDefaultName:
300 if ( nListSize >= 1 )
302 setKeepLocation( true );
303 setSelection( pStringList->front() );
305 break;
306 case CommandEvent::SetMultiSelection:
307 if ( nListSize >= 1 )
309 if ( pStringList->front() == "true" )
310 setMode( KFile::Files );
311 else
312 setMode( KFile::File );
314 break;
315 case CommandEvent::Exec:
317 filterWidget->setEditable( false );
318 setIsExecuting( true );
319 bool bCanExit = false;
320 do {
321 setCanNotifySelection( true );
322 exec();
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() ) );
332 bCanExit =
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 ) );
344 bCanExit = false;
346 else
347 bCanExit = true;
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 ) );
354 bCanExit = true;
356 else
357 bCanExit = true;
358 } while ( !bCanExit );
359 setIsExecuting( false );
361 if ( result() == QDialog::Accepted )
362 sendCommand( "accept" );
363 else
364 sendCommand( "reject" );
366 break;
367 default:
368 break;
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 );
415 delete pList;
417 return pWidget;
420 void FileDialog::enableControl( const QString &rId, const QString &rValue )
422 QWidget *pWidget = findControl( rId );
424 if ( pWidget )
425 pWidget->setEnabled( rValue.lower() == "true" );
428 void FileDialog::getValue( const QString &rId, const QString &rAction )
430 QWidget *pWidget = findControl( rId );
431 QString qString;
432 qString.reserve( 1024 );
433 qString.append( "value" );
435 if ( pWidget )
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" );
444 else
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() ) );
469 // TODO getHelpURL
471 // TODO push button
474 sendCommand( qString );
477 void FileDialog::setValue( const QString &rId, const QString &rAction, const QStringList &rValue )
479 QWidget *pWidget = findControl( rId );
481 if ( pWidget )
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" )
510 pComboBox->clear();
512 else if ( rAction == "setSelectedItem" )
514 if ( !rValue.isEmpty() )
515 pComboBox->setCurrentItem( rValue.front().toInt() );
517 // FIXME setHelpURL is ignored
519 // TODO push button
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 )
543 if ( bFirstFilter )
544 bFirstFilter = false;
545 else
546 qString.append( '\n' );
548 qString.append( (*it).second );
549 qString.append( '|' );
551 qTmp = (*it).first;
552 qString.append( qTmp.replace( '/', "\\/" ) );
555 return qString;
558 QString FileDialog::addExtension( const QString &rFileName ) const
560 if ( !isSave() )
561 return rFileName;
563 QString qExtension;
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();
573 ++it )
575 int nUnwanted = (*it).findRev( '*' );
576 if ( nUnwanted < 0 )
577 nUnwanted = (*it).findRev( '?' );
578 else
579 nUnwanted = ::std::max( nUnwanted, (*it).find( '?', nUnwanted ) );
581 int nIdx = (*it).find( '.', ::std::max( nUnwanted, 0 ) );
582 if ( nIdx >= 0 )
583 qExtension = (*it).mid( nIdx ).lower();
587 if ( qExtension.isEmpty() || qExtension == "." || rFileName.endsWith( qExtension ) )
588 return rFileName;
589 else
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",
602 "telnet",
603 NULL };
605 for ( const char **pIndex = pOOoProtocols; *pIndex != NULL; ++pIndex )
607 if ( rProtocol == *pIndex )
608 return true;
611 // TODO gnome-vfs bits here
613 return false;
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;
622 else
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;
633 #endif
635 return rURL;
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 );
648 else
649 nExtensionPos = rFileName.find( '.' );
651 KTempFile qTempFile( QString::null, ( nExtensionPos < 0 )? QString(): rFileName.mid( nExtensionPos ) );
652 KURL qDestURL;
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;
686 #endif
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" );
728 else
729 rBuffer.append( *pUnicode );
731 rBuffer.append( '"' );
734 QString FileDialog::escapeString( const QString &rString )
736 QString qString;
737 qString.reserve( 2*rString.length() + 2 ); // every char escaped + quotes
739 appendEscaped( qString, rString );
741 return qString;
745 void FileFilterComboHack::setCurrentFilter( const QString& filter )
747 setCurrentText( filter );
748 filterChanged();
750 // Workaround for 'Filter name (*.blah)' vs. 'Filter name'
751 if ( currentText() != text( currentItem() ) )
753 int nItem = 0;
754 for ( ; nItem < count() && !text( nItem ).startsWith( filter ); ++nItem );
756 if ( nItem < count() )
757 setCurrentItem( nItem );
758 else
759 setCurrentItem( 0 );
761 filterChanged();