1 /*
2     SPDX-FileCopyrightText: 2003-2008 Sebastian Trueg <trueg@k3b.org>
3     SPDX-FileCopyrightText: 2009 Gustavo Pichorim Boiko <gustavo.boiko@kdemail.net>
4     SPDX-FileCopyrightText: 2009-2011 Michal Malek <michalm@jabster.pl>
5     SPDX-FileCopyrightText: 1998-2009 Sebastian Trueg <trueg@k3b.org>
6 
7     SPDX-License-Identifier: GPL-2.0-or-later
8 */
9 
10 #include "k3bdataviewimpl.h"
11 #include "k3bbootimagedialog.h"
12 #include "k3bdatadoc.h"
13 #include "k3bdatamultisessionimportdialog.h"
14 #include "k3bdataprojectdelegate.h"
15 #include "k3bdataprojectmodel.h"
16 #include "k3bdataprojectsortproxymodel.h"
17 #include "k3bdatapropertiesdialog.h"
18 #include "k3bdataurladdingdialog.h"
19 #include "k3bdiritem.h"
20 #include "k3bview.h"
21 #include "k3bviewcolumnadjuster.h"
22 #include "k3bvolumenamewidget.h"
23 
24 #include <KLocalizedString>
25 #include <KFileItemDelegate>
26 #include <KActionCollection>
27 #include <KIO/JobUiDelegate>
28 #include <KIO/OpenUrlJob>
29 
30 #include <QSortFilterProxyModel>
31 #include <QAction>
32 #include <QDialog>
33 #include <QDialogButtonBox>
34 #include <QInputDialog>
35 #include <QShortcut>
36 #include <QWidgetAction>
37 
38 
39 namespace {
40 
41 class VolumeNameWidgetAction : public QWidgetAction
42 {
43 public:
VolumeNameWidgetAction(K3b::DataDoc * doc,QObject * parent)44     VolumeNameWidgetAction( K3b::DataDoc* doc, QObject* parent )
45     :
46         QWidgetAction( parent ),
47         m_doc( doc )
48     {
49     }
50 
51 protected:
createWidget(QWidget * parent)52     QWidget* createWidget( QWidget* parent ) override
53     {
54         return new K3b::VolumeNameWidget( m_doc, parent );
55     }
56 
57 private:
58     K3b::DataDoc* m_doc;
59 };
60 
61 } // namespace
62 
63 
DataViewImpl(View * view,DataDoc * doc,KActionCollection * actionCollection)64 K3b::DataViewImpl::DataViewImpl( View* view, DataDoc* doc, KActionCollection* actionCollection )
65 :
66     QObject( view ),
67     m_view( view ),
68     m_doc( doc ),
69     m_model( new DataProjectModel( doc, view ) ),
70     m_sortModel( new DataProjectSortProxyModel( this ) ),
71     m_fileView( new QTreeView( view ) )
72 {
73     connect( m_doc, SIGNAL(importedSessionChanged(int)), this, SLOT(slotImportedSessionChanged(int)) );
74     connect( m_model, SIGNAL(addUrlsRequested(QList<QUrl>,K3b::DirItem*)), SLOT(slotAddUrlsRequested(QList<QUrl>,K3b::DirItem*)) );
75     connect( m_model, SIGNAL(moveItemsRequested(QList<K3b::DataItem*>,K3b::DirItem*)), SLOT(slotMoveItemsRequested(QList<K3b::DataItem*>,K3b::DirItem*)) );
76 
77     m_sortModel->setSourceModel( m_model );
78 
79     m_fileView->setItemDelegate( new DataProjectDelegate( this ) );
80     m_fileView->setModel( m_sortModel );
81     m_fileView->setAcceptDrops( true );
82     m_fileView->setDragEnabled( true );
83     m_fileView->setDragDropMode( QTreeView::DragDrop );
84     m_fileView->setItemsExpandable( false );
85     m_fileView->setRootIsDecorated( false );
86     m_fileView->setSelectionMode( QTreeView::ExtendedSelection );
87     m_fileView->setVerticalScrollMode( QAbstractItemView::ScrollPerPixel );
88     m_fileView->setContextMenuPolicy( Qt::ActionsContextMenu );
89     m_fileView->setSortingEnabled( true );
90     m_fileView->sortByColumn( DataProjectModel::FilenameColumn, Qt::AscendingOrder );
91     m_fileView->setMouseTracking( true );
92     m_fileView->setAllColumnsShowFocus( true );
93     connect( m_fileView, SIGNAL(doubleClicked(QModelIndex)),
94              this, SLOT(slotItemActivated(QModelIndex)) );
95     connect( m_fileView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
96              this, SLOT(slotSelectionChanged()) );
97 
98     m_columnAdjuster = new ViewColumnAdjuster( this );
99     m_columnAdjuster->setView( m_fileView );
100     m_columnAdjuster->addFixedColumn( DataProjectModel::TypeColumn );
101     m_columnAdjuster->setColumnMargin( DataProjectModel::TypeColumn, 10 );
102     m_columnAdjuster->addFixedColumn( DataProjectModel::SizeColumn );
103     m_columnAdjuster->setColumnMargin( DataProjectModel::SizeColumn, 10 );
104 
105     m_actionNewDir = new QAction( QIcon::fromTheme( "folder-new" ), i18n("New Folder..."), m_fileView );
106     m_actionNewDir->setShortcut( Qt::CTRL + Qt::Key_N );
107     m_actionNewDir->setShortcutContext( Qt::WidgetShortcut );
108     actionCollection->addAction( "new_dir", m_actionNewDir );
109     connect( m_actionNewDir, SIGNAL(triggered(bool)), this, SLOT(slotNewDir()) );
110 
111     m_actionRemove = new QAction( QIcon::fromTheme( "edit-delete" ), i18n("Remove"), m_fileView );
112     m_actionRemove->setShortcut( Qt::Key_Delete );
113     m_actionRemove->setShortcutContext( Qt::WidgetShortcut );
114     actionCollection->addAction( "remove", m_actionRemove );
115     connect( m_actionRemove, SIGNAL(triggered(bool)), this, SLOT(slotRemove()) );
116 
117     m_actionRename = new QAction( QIcon::fromTheme( "edit-rename" ), i18n("Rename"), m_fileView );
118     m_actionRename->setShortcut( Qt::Key_F2 );
119     m_actionRename->setShortcutContext( Qt::WidgetShortcut );
120     actionCollection->addAction( "rename", m_actionRename );
121     connect( m_actionRename, SIGNAL(triggered(bool)), this, SLOT(slotRename()) );
122 
123     m_actionParentDir = new QAction( QIcon::fromTheme( "go-up" ), i18n("Parent Folder"), m_fileView );
124     m_actionParentDir->setShortcut( Qt::Key_Backspace );
125     m_actionParentDir->setShortcutContext( Qt::WidgetShortcut );
126     actionCollection->addAction( "parent_dir", m_actionParentDir );
127 
128     m_actionProperties = new QAction( QIcon::fromTheme( "document-properties" ), i18n("Properties"), m_fileView );
129     m_actionProperties->setShortcut( Qt::ALT + Qt::Key_Return );
130     m_actionProperties->setShortcutContext( Qt::WidgetShortcut );
131     actionCollection->addAction( "properties", m_actionProperties );
132     connect( m_actionProperties, SIGNAL(triggered(bool)), this, SLOT(slotProperties()) );
133 
134     m_actionOpen = new QAction( QIcon::fromTheme( "document-open" ), i18n("Open"), m_view );
135     actionCollection->addAction( "open", m_actionOpen );
136     connect( m_actionOpen, SIGNAL(triggered(bool)), this, SLOT(slotOpen()) );
137 
138     m_actionImportSession = new QAction( QIcon::fromTheme( "document-import" ), i18n("&Import Session..."), m_view );
139     m_actionImportSession->setToolTip( i18n("Import a previously burned session into the current project") );
140     actionCollection->addAction( "project_data_import_session", m_actionImportSession );
141     connect( m_actionImportSession, SIGNAL(triggered(bool)), this, SLOT(slotImportSession()) );
142 
143     m_actionClearSession = new QAction( QIcon::fromTheme( "edit-clear" ), i18n("&Clear Imported Session"), m_view );
144     m_actionClearSession->setToolTip( i18n("Remove the imported items from a previous session") );
145     m_actionClearSession->setEnabled( m_doc->importedSession() > -1 );
146     actionCollection->addAction( "project_data_clear_imported_session", m_actionClearSession );
147     connect( m_actionClearSession, SIGNAL(triggered(bool)), this, SLOT(slotClearImportedSession()) );
148 
149     m_actionEditBootImages = new QAction( QIcon::fromTheme( "document-properties" ), i18n("&Edit Boot Images..."), m_view );
150     m_actionEditBootImages->setToolTip( i18n("Modify the bootable settings of the current project") );
151     actionCollection->addAction( "project_data_edit_boot_images", m_actionEditBootImages );
152     connect( m_actionEditBootImages, SIGNAL(triggered(bool)), this, SLOT(slotEditBootImages()) );
153 
154     QWidgetAction* volumeNameWidgetAction = new VolumeNameWidgetAction( m_doc, this );
155     actionCollection->addAction( "project_volume_name", volumeNameWidgetAction );
156 
157     QShortcut* enterShortcut = new QShortcut( QKeySequence( Qt::Key_Return ), m_fileView );
158     enterShortcut->setContext( Qt::WidgetShortcut );
159     connect( enterShortcut, SIGNAL(activated()), this, SLOT(slotEnterPressed()) );
160 
161     // Create data context menu
162     QAction* separator = new QAction( this );
163     separator->setSeparator( true );
164     m_fileView->addAction( m_actionParentDir );
165     m_fileView->addAction( separator );
166     m_fileView->addAction( m_actionRename );
167     m_fileView->addAction( m_actionRemove );
168     m_fileView->addAction( m_actionNewDir );
169     m_fileView->addAction( separator );
170     m_fileView->addAction( m_actionOpen );
171     m_fileView->addAction( separator );
172     m_fileView->addAction( m_actionProperties );
173     m_fileView->addAction( separator );
174     m_fileView->addAction( actionCollection->action("project_burn") );
175 }
176 
177 
addUrls(const QModelIndex & parent,const QList<QUrl> & urls)178 void K3b::DataViewImpl::addUrls( const QModelIndex& parent, const QList<QUrl>& urls )
179 {
180     DirItem *item = dynamic_cast<DirItem*>( m_model->itemForIndex( parent ) );
181     if (!item)
182         item = m_doc->root();
183 
184     DataUrlAddingDialog::addUrls( urls, item, m_view );
185 }
186 
187 
slotCurrentRootChanged(const QModelIndex & newRoot)188 void K3b::DataViewImpl::slotCurrentRootChanged( const QModelIndex& newRoot )
189 {
190     // make the file view show only the child nodes of the currently selected
191     // directory from dir view
192     m_fileView->setRootIndex( m_sortModel->mapFromSource( newRoot ) );
193     m_columnAdjuster->adjustColumns();
194     m_actionParentDir->setEnabled( newRoot.isValid() && m_model->indexForItem( m_doc->root() ) != newRoot );
195 }
196 
197 
slotNewDir()198 void K3b::DataViewImpl::slotNewDir()
199 {
200     const QModelIndex parent = m_sortModel->mapToSource( m_fileView->rootIndex() );
201     DirItem* parentDir = 0;
202 
203     if (parent.isValid())
204         parentDir = dynamic_cast<DirItem*>( m_model->itemForIndex( parent ) );
205 
206     if (!parentDir)
207         parentDir = m_doc->root();
208 
209     QString name;
210     bool ok;
211 
212     name = QInputDialog::getText( m_view,
213                                   i18n("New Folder"),
214                                   i18n("Please insert the name for the new folder:"),
215                                   QLineEdit::Normal,
216                                   i18n("New Folder"),
217                                   &ok );
218 
219     while( ok && DataDoc::nameAlreadyInDir( name, parentDir ) ) {
220         name = QInputDialog::getText( m_view,
221                                       i18n("New Folder"),
222                                       i18n("A file with that name already exists. "
223                                            "Please insert the name for the new folder:"),
224                                       QLineEdit::Normal,
225                                       i18n("New Folder"),
226                                       &ok );
227     }
228 
229     if( !ok )
230         return;
231 
232     m_doc->addEmptyDir( name, parentDir );
233 }
234 
235 
slotRemove()236 void K3b::DataViewImpl::slotRemove()
237 {
238     // Remove items directly from sort model to avoid unnecessary mapping of indexes
239     const QItemSelection selection = m_fileView->selectionModel()->selection();
240     const QModelIndex parentDirectory = m_fileView->rootIndex();
241     for( int i = selection.size() - 1; i >= 0; --i ) {
242         m_sortModel->removeRows( selection.at(i).top(), selection.at(i).height(), parentDirectory );
243     }
244 }
245 
246 
slotRename()247 void K3b::DataViewImpl::slotRename()
248 {
249     const QModelIndex current = m_fileView->currentIndex();
250     if( current.isValid() ) {
251         m_fileView->edit( current );
252     }
253 }
254 
255 
slotProperties()256 void K3b::DataViewImpl::slotProperties()
257 {
258     const QModelIndexList indices = m_fileView->selectionModel()->selectedRows();
259     if ( indices.isEmpty() )
260     {
261         // show project properties
262         m_view->slotProperties();
263     }
264     else
265     {
266         QList<DataItem*> items;
267 
268         foreach(const QModelIndex& index, indices) {
269             items.append( m_model->itemForIndex( m_sortModel->mapToSource( index ) ) );
270         }
271 
272         DataPropertiesDialog dlg( items, m_view );
273         dlg.exec();
274     }
275 }
276 
277 
slotOpen()278 void K3b::DataViewImpl::slotOpen()
279 {
280     const QModelIndex current = m_sortModel->mapToSource( m_fileView->currentIndex() );
281     if( !current.isValid() )
282         return;
283 
284     DataItem* item = m_model->itemForIndex( current );
285 
286     if( !item->isFile() ) {
287         QUrl url = QUrl::fromLocalFile( item->localPath() );
288         auto *job = new KIO::OpenUrlJob( url, item->mimeType().name() );
289         job->setRunExecutables( false ); // this is the default, but let's make really sure :)
290         job->setUiDelegate( new KIO::JobUiDelegate( KJobUiDelegate::AutoHandlingEnabled, m_view ) );
291         job->start();
292     }
293 }
294 
295 
slotSelectionChanged()296 void K3b::DataViewImpl::slotSelectionChanged()
297 {
298     const QModelIndexList indexes = m_fileView->selectionModel()->selectedRows();
299 
300     bool open = true, rename = true, remove = true;
301 
302     // we can only rename one item at a time
303     // also, we can only create a new dir over a single directory
304     if (indexes.count() > 1)
305     {
306         rename = false;
307         open = false;
308     }
309     else if (indexes.count() == 1)
310     {
311         QModelIndex index = indexes.first();
312         rename = (index.flags() & Qt::ItemIsEditable);
313         open = (index.data(DataProjectModel::ItemTypeRole).toInt() == DataProjectModel::FileItemType);
314     }
315     else // selectedIndex.count() == 0
316     {
317         remove = false;
318         rename = false;
319         open = false;
320     }
321 
322     // check if all selected items can be removed
323     foreach(const QModelIndex &index, indexes)
324     {
325         if (!(index.data(DataProjectModel::CustomFlagsRole).toInt() & DataProjectModel::ItemIsRemovable))
326         {
327             remove = false;
328             break;
329         }
330     }
331 
332     m_actionRename->setEnabled( rename );
333     m_actionRemove->setEnabled( remove );
334     m_actionOpen->setEnabled( open );
335 }
336 
337 
slotItemActivated(const QModelIndex & index)338 void K3b::DataViewImpl::slotItemActivated( const QModelIndex& index )
339 {
340     if( index.isValid() ) {
341         const int type = index.data( DataProjectModel::ItemTypeRole ).toInt();
342         if( type == DataProjectModel::DirItemType ) {
343             emit setCurrentRoot( m_sortModel->mapToSource( index ) );
344         }
345         else if( type == DataProjectModel::FileItemType ) {
346             m_fileView->edit( index );
347         }
348     }
349 }
350 
351 
slotEnterPressed()352 void K3b::DataViewImpl::slotEnterPressed()
353 {
354     slotItemActivated( m_fileView->currentIndex() );
355 }
356 
357 
slotImportSession()358 void K3b::DataViewImpl::slotImportSession()
359 {
360     K3b::DataMultisessionImportDialog::importSession( m_doc, m_view );
361 }
362 
363 
slotClearImportedSession()364 void K3b::DataViewImpl::slotClearImportedSession()
365 {
366     m_doc->clearImportedSession();
367 }
368 
369 
slotEditBootImages()370 void K3b::DataViewImpl::slotEditBootImages()
371 {
372     BootImageDialog dlg( m_doc );
373     dlg.setWindowTitle( i18n("Edit Boot Images") );
374     dlg.exec();
375 }
376 
377 
slotImportedSessionChanged(int importedSession)378 void K3b::DataViewImpl::slotImportedSessionChanged( int importedSession )
379 {
380     m_actionClearSession->setEnabled( importedSession > -1 );
381 }
382 
383 
slotAddUrlsRequested(QList<QUrl> urls,K3b::DirItem * targetDir)384 void K3b::DataViewImpl::slotAddUrlsRequested( QList<QUrl> urls, K3b::DirItem* targetDir )
385 {
386     DataUrlAddingDialog::addUrls( urls, targetDir, m_view );
387 }
388 
389 
slotMoveItemsRequested(QList<K3b::DataItem * > items,K3b::DirItem * targetDir)390 void K3b::DataViewImpl::slotMoveItemsRequested( QList<K3b::DataItem*> items, K3b::DirItem* targetDir )
391 {
392     DataUrlAddingDialog::moveItems( items, targetDir, m_view );
393 }
394 
395 
396