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