/*************************************************************************** * Copyright (C) 2008 by Volker Lanz * * * * 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; either version 2 of the License, or * * (at your option) any later version. * * * * 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; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ #include "gui/mainwindow.h" #include "gui/partwidget.h" #include "gui/partpropsdialog.h" #include "gui/resizedialog.h" #include "gui/infopane.h" #include "gui/newdialog.h" #include "gui/filesystemsupportdialog.h" #include "gui/progressdialog.h" #include "gui/insertdialog.h" #include "core/partition.h" #include "core/device.h" #include "core/operationstack.h" #include "core/partitiontable.h" #include "core/operationrunner.h" #include "fs/filesystemfactory.h" #include "ops/deleteoperation.h" #include "ops/resizeoperation.h" #include "ops/newoperation.h" #include "ops/copyoperation.h" #include "ops/createpartitiontableoperation.h" #include "ops/checkoperation.h" #include "ops/backupoperation.h" #include "ops/restoreoperation.h" #include "ops/setfilesystemlabeloperation.h" #include "ops/setpartflagsoperation.h" #include "ops/createfilesystemoperation.h" #include "util/globallog.h" #include "util/capacity.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include class PartitionTreeWidgetItem : public QTreeWidgetItem { public: PartitionTreeWidgetItem(const Partition* p) : QTreeWidgetItem(), m_Partition(p) {} const Partition* partition() const { return m_Partition; } private: const Partition* m_Partition; }; /** Creates a new MainWindow instance. @param parent the parent widget */ MainWindow::MainWindow(QWidget* parent) : KXmlGuiWindow(parent), Ui::MainWindowBase(), m_LibParted(), m_OperationStack(), m_OperationRunner(operationStack()), m_StatusText(new QLabel(this)), m_InfoPane(new InfoPane(this)), m_ClipboardPartition(NULL), m_ProgressDialog(new ProgressDialog(this, operationRunner())) { setupUi(this); FileSystemFactory::init(); connect(GlobalLog::instance(), SIGNAL(newMessage(log::Level, const QString&)), SLOT(onNewLogMessage(log::Level, const QString&))); setupActions(); setupStatusBar(); setupConnections(); setupGUI(); loadConfig(); dockInformation().setWidget(&infoPane()); treePartitions().header()->setStretchLastSection(false); scanDevices(); } void MainWindow::closeEvent(QCloseEvent* event) { if (progressDialog().isVisible()) { event->ignore(); return; } if (operationStack().size() > 0) { if (KMessageBox::warningContinueCancel(this, i18ncp("@info", "Do you really want to quit the application?There is still an operation pending.", "Do you really want to quit the application?There are still %1 operations pending.", operationStack().size()), i18nc("@title:window", "Discard pending operations and quit?"), KGuiItem(i18nc("@action:button", "&Quit %1", KGlobal::mainComponent().aboutData()->programName())), KStandardGuiItem::cancel(), "reallyQuit") == KMessageBox::Cancel) { event->ignore(); return; } } saveConfig(); KXmlGuiWindow::closeEvent(event); } void MainWindow::changeEvent(QEvent* event) { if ((event->type() == QEvent::ActivationChange || event->type() == QEvent::WindowStateChange) && event->spontaneous() && isActiveWindow() && progressDialog().isVisible()) { progressDialog().activateWindow(); progressDialog().raise(); } KXmlGuiWindow::changeEvent(event); } void MainWindow::setupActions() { // File actions KStandardAction::quit(this, SLOT(close()), actionCollection()); // Edit actions KAction* undoOperation = actionCollection()->addAction("undoOperation", this, SLOT(onUndoOperation())); undoOperation->setEnabled(false); undoOperation->setText(i18nc("@action:inmenu", "Undo")); undoOperation->setToolTip(i18nc("@info:tooltip", "Undo the last operation")); undoOperation->setStatusTip(i18nc("@info:status", "Remove the last operation from the list.")); undoOperation->setShortcut(Qt::CTRL | Qt::Key_Z); undoOperation->setIcon(BarIcon("edit-undo")); KAction* clearAllOperations = actionCollection()->addAction("clearAllOperations", this, SLOT(onClearAllOperations())); clearAllOperations->setEnabled(false); clearAllOperations->setText(i18nc("@action:inmenu clear the list of operations", "Clear")); clearAllOperations->setToolTip(i18nc("@info:tooltip", "Clear all operations")); clearAllOperations->setStatusTip(i18nc("@info:status", "Empty the list of pending operations.")); clearAllOperations->setIcon(BarIcon("dialog-cancel")); KAction* applyAllOperations = actionCollection()->addAction("applyAllOperations", this, SLOT(onApplyAllOperations())); applyAllOperations->setEnabled(false); applyAllOperations->setText(i18nc("@action:inmenu apply all operations", "Apply")); applyAllOperations->setToolTip(i18nc("@info:tooltip", "Apply all operations")); applyAllOperations->setStatusTip(i18nc("@info:status", "Apply the pending operations in the list.")); applyAllOperations->setIcon(BarIcon("dialog-ok-apply")); // View actions actionCollection()->addAction("toggleDockDevices", dockDevices().toggleViewAction()); actionCollection()->addAction("toggleDockOperations", dockOperations().toggleViewAction()); actionCollection()->addAction("toggleDockInformation", dockInformation().toggleViewAction()); actionCollection()->addAction("toggleDockLog", dockLog().toggleViewAction()); KAction* fileSystemSupport = actionCollection()->addAction("fileSystemSupport", this, SLOT(onFileSystemSupport())); fileSystemSupport->setText(i18nc("@action:inmenu", "File System Support")); fileSystemSupport->setToolTip(i18nc("@info:tooltip", "View file system support information")); fileSystemSupport->setStatusTip(i18nc("@info:status", "Show information about supported file systems.")); // Device actions KAction* refreshDevices = actionCollection()->addAction("refreshDevices", this, SLOT(onRefreshDevices())); refreshDevices->setText(i18nc("@action:inmenu refresh list of devices", "Refresh Devices")); refreshDevices->setToolTip(i18nc("@info:tooltip", "Refresh all devices")); refreshDevices->setStatusTip(i18nc("@info:status", "Renew the devices list.")); refreshDevices->setShortcut(Qt::Key_F5); refreshDevices->setIcon(BarIcon("view-refresh")); KAction* createNewPartitionTable = actionCollection()->addAction("createNewPartitionTable", this, SLOT(onCreateNewPartitionTable())); createNewPartitionTable->setEnabled(false); createNewPartitionTable->setText(i18nc("@action:inmenu", "Create New Partition Table")); createNewPartitionTable->setToolTip(i18nc("@info:tooltip", "Create New Partition Table")); createNewPartitionTable->setStatusTip(i18nc("@info:status", "Create a new and empty partition table on a device.")); createNewPartitionTable->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_N); createNewPartitionTable->setIcon(BarIcon("edit-clear")); // Partition actions KAction* newPartition = actionCollection()->addAction("newPartition", this, SLOT(onNewPartition())); newPartition->setEnabled(false); newPartition->setText(i18nc("@action:inmenu create a new partition", "New")); newPartition->setToolTip(i18nc("@info:tooltip", "New partition")); newPartition->setStatusTip(i18nc("@info:status", "Create a new partition.")); newPartition->setShortcut(Qt::CTRL | Qt::Key_N); newPartition->setIcon(BarIcon("document-new")); KAction* resizePartition = actionCollection()->addAction("resizePartition", this, SLOT(onResizePartition())); resizePartition->setEnabled(false); resizePartition->setText(i18nc("@action:inmenu", "Resize/Move")); resizePartition->setToolTip(i18nc("@info:tooltip", "Resize or move partition")); resizePartition->setStatusTip(i18nc("@info:status", "Shrink, grow or move an existing partition.")); resizePartition->setShortcut(Qt::CTRL | Qt::Key_R); resizePartition->setIcon(BarIcon("arrow-right-double")); KAction* deletePartition = actionCollection()->addAction("deletePartition", this, SLOT(onDeletePartition())); deletePartition->setEnabled(false); deletePartition->setText(i18nc("@action:inmenu", "Delete")); deletePartition->setToolTip(i18nc("@info:tooltip", "Delete partition")); deletePartition->setStatusTip(i18nc("@info:status", "Delete a partition.")); deletePartition->setShortcut(Qt::Key_Delete); deletePartition->setIcon(BarIcon("edit-delete-shred")); KAction* copyPartition = actionCollection()->addAction("copyPartition", this, SLOT(onCopyPartition())); copyPartition->setEnabled(false); copyPartition->setText(i18nc("@action:inmenu", "Copy")); copyPartition->setToolTip(i18nc("@info:tooltip", "Copy partition")); copyPartition->setStatusTip(i18nc("@info:status", "Copy an existing partition.")); copyPartition->setShortcut(Qt::CTRL | Qt::Key_C); copyPartition->setIcon(BarIcon("edit-copy")); KAction* pastePartition = actionCollection()->addAction("pastePartition", this, SLOT(onPastePartition())); pastePartition->setEnabled(false); pastePartition->setText(i18nc("@action:inmenu", "Paste")); pastePartition->setToolTip(i18nc("@info:tooltip", "Paste partition")); pastePartition->setStatusTip(i18nc("@info:status", "Paste a copied partition.")); pastePartition->setShortcut(Qt::CTRL | Qt::Key_V); pastePartition->setIcon(BarIcon("edit-paste")); KAction* mountPartition = actionCollection()->addAction("mountPartition", this, SLOT(onMountPartition())); mountPartition->setEnabled(false); mountPartition->setText(i18nc("@action:inmenu", "Mount")); mountPartition->setToolTip(i18nc("@info:tooltip", "Mount or unmount partition")); mountPartition->setStatusTip(i18nc("@info:status", "Mount or unmount a partition.")); KAction* checkPartition = actionCollection()->addAction("checkPartition", this, SLOT(onCheckPartition())); checkPartition->setEnabled(false); checkPartition->setText(i18nc("@action:inmenu", "Check")); checkPartition->setToolTip(i18nc("@info:tooltip", "Check partition")); checkPartition->setStatusTip(i18nc("@info:status", "Check a filesystem on a partition for errors.")); checkPartition->setIcon(BarIcon("flag")); KAction* propertiesPartition = actionCollection()->addAction("propertiesPartition", this, SLOT(onPropertiesPartition())); propertiesPartition->setEnabled(false); propertiesPartition->setText(i18nc("@action:inmenu", "Properties")); propertiesPartition->setToolTip(i18nc("@info:tooltip", "Show properties dialog")); propertiesPartition->setStatusTip(i18nc("@info:status", "View and modify partition properties (label, partition flags, etc.)")); propertiesPartition->setIcon(BarIcon("document-properties")); KAction* backup = actionCollection()->addAction("backupPartition", this, SLOT(onBackupPartition())); backup->setEnabled(false); backup->setText(i18nc("@action:inmenu", "Backup")); backup->setToolTip(i18nc("@info:tooltip", "Backup partition")); backup->setStatusTip(i18nc("@info:status", "Backup a partition to an image file.")); backup->setIcon(BarIcon("document-export")); KAction* restore = actionCollection()->addAction("restorePartition", this, SLOT(onRestorePartition())); restore->setEnabled(false); restore->setText(i18nc("@action:inmenu", "Restore")); restore->setToolTip(i18nc("@info:tooltip", "Restore partition")); restore->setStatusTip(i18nc("@info:status", "Restore a partition from an image file.")); restore->setIcon(BarIcon("document-import")); } void MainWindow::setupConnections() { connect(&partTableWidget(), SIGNAL(itemActivated(const PartWidget*)), actionCollection()->action("propertiesPartition"), SLOT(trigger())); connect(&progressDialog(), SIGNAL(finished(int)), SLOT(onFinished())); connect(this, SIGNAL(devicesChanged()), SLOT(scanDevices())); } void MainWindow::setupStatusBar() { statusBar()->addWidget(&statusText()); updateStatusBar(); } void MainWindow::loadConfig() { QList colWidths = Config::treePartitionColumnWidths(); if (!colWidths.isEmpty() && colWidths[0] != -1) for (int i = 0; i < colWidths.size(); i++) treePartitions().setColumnWidth(i, colWidths[i]); bool firstRun = Config::firstRun(); if (firstRun) { dockLog().setVisible(false); dockInformation().setVisible(false); toolBar("deviceToolBar")->setVisible(false); } } void MainWindow::saveConfig() const { QList colWidths; for(int i = 0; i < treePartitions().columnCount(); i++) colWidths.append(treePartitions().columnWidth(i)); Config::setTreePartitionColumnWidths(colWidths); Config::setFirstRun(false); Config::self()->writeConfig(); } void MainWindow::updateStatusBar() { statusText().setText(i18ncp("@info:status", "One pending operation", "%1 pending operations", operationStack().size())); } void MainWindow::scanDevices() { log() << i18nc("@info/plain", "Rescan devices..."); KApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); const qint64 selectedDeviceIdx = selectedDevice() ? listDevices().currentRow() : -1; listDevices().clearSelection(); setClipboardPartition(NULL); partTableWidget().clear(); libParted().scanDevices(operationStack()); setupDevicesList(); updatePartitions(); updateOperations(); updateStatusBar(); if (selectedDeviceIdx > -1 && selectedDeviceIdx < listDevices().count()) listDevices().setCurrentRow(selectedDeviceIdx); log() << i18nc("@info/plain", "Rescan finished."); KApplication::restoreOverrideCursor(); } void MainWindow::setupDevicesList() { listDevices().clear(); foreach(const Device* d, operationStack().previewDevices()) { const QString shortText = d->deviceNode() + " (" + Capacity(*d).toString() + ')'; const QString longText = d->deviceNode() + " (" + Capacity(*d).toString() + ", " + d->name() + ')'; QListWidgetItem* item = new QListWidgetItem(SmallIcon("drive-harddisk"), shortText); item->setToolTip(longText); listDevices().addItem(item); } enableActions(); } void MainWindow::enableActions() { actionCollection()->action("createNewPartitionTable")->setEnabled(CreatePartitionTableOperation::canCreate(selectedDevice())); const Partition* part = selectedPartition(); const bool readOnly = selectedDevice() == NULL || selectedDevice()->partitionTable().isReadOnly(); actionCollection()->action("newPartition")->setEnabled(!readOnly && NewOperation::canCreateNew(part)); const bool canResize = ResizeOperation::canGrow(part) || ResizeOperation::canShrink(part) || ResizeOperation::canMove(part); actionCollection()->action("resizePartition")->setEnabled(!readOnly && canResize); actionCollection()->action("copyPartition")->setEnabled(CopyOperation::canCopy(part)); actionCollection()->action("deletePartition")->setEnabled(!readOnly && DeleteOperation::canDelete(part)); actionCollection()->action("pastePartition")->setEnabled(!readOnly && CopyOperation::canPaste(part, clipboardPartition())); actionCollection()->action("propertiesPartition")->setEnabled(part != NULL); actionCollection()->action("mountPartition")->setEnabled(part && (part->canMount() || part->canUnmount())); if (part != NULL) actionCollection()->action("mountPartition")->setText(part->isMounted() ? part->fileSystem().unmountTitle() : part->fileSystem().mountTitle() ); actionCollection()->action("checkPartition")->setEnabled(!readOnly && CheckOperation::canCheck(part)); actionCollection()->action("undoOperation")->setEnabled(operationStack().size() > 0); actionCollection()->action("clearAllOperations")->setEnabled(operationStack().size() > 0); actionCollection()->action("applyAllOperations")->setEnabled(operationStack().size() > 0 && geteuid() == 0); actionCollection()->action("backupPartition")->setEnabled(BackupOperation::canBackup(part)); actionCollection()->action("restorePartition")->setEnabled(RestoreOperation::canRestore(part)); } void MainWindow::on_m_ListDevices_itemClicked() { treePartitions().setCurrentItem(NULL); enableActions(); updatePartitions(); } void MainWindow::updateDevices() { enableActions(); updatePartitions(); if (selectedDevice()) infoPane().showDevice(*selectedDevice()); else infoPane().clear(); } void MainWindow::on_m_ListDevices_itemSelectionChanged() { updateDevices(); } void MainWindow::updateOperations() { listOperations().clear(); foreach (const Operation* op, operationStack().operations()) { QListWidgetItem* item = new QListWidgetItem(SmallIcon(op->iconName()), op->description()); item->setToolTip(op->description()); listOperations().addItem(item); } listOperations().scrollToBottom(); } static QTreeWidgetItem* createTreeWidgetItem(const Partition& p) { QTreeWidgetItem* item = new PartitionTreeWidgetItem(&p); item->setText(0, p.deviceNode()); item->setText(1, p.fileSystem().name()); item->setText(2, p.mountPoints().join(", ")); if (p.isMounted()) item->setIcon(2, SmallIcon("object-locked")); item->setText(3, p.fileSystem().label()); item->setText(4, Capacity(p).toString()); item->setText(5, Capacity(p, Capacity::Used).toString()); item->setText(6, PartitionTable::flagNames(p.activeFlags()).join(", ")); return item; } void MainWindow::updateWindowTitle() { QString title; if (selectedDevice()) title = selectedDevice()->deviceNode() + " - "; title += KGlobal::mainComponent().aboutData()->programName() + ' ' + KGlobal::mainComponent().aboutData()->version(); setWindowTitle(title); } void MainWindow::updatePartitions() { treePartitions().clear(); partTableWidget().clear(); updateWindowTitle(); if (selectedDevice() == NULL) return; partTableWidget().setPartitionTable(&selectedDevice()->partitionTable()); QTreeWidgetItem* deviceItem = new QTreeWidgetItem(); deviceItem->setText(0, selectedDevice()->name()); deviceItem->setIcon(0, SmallIcon("drive-harddisk")); treePartitions().addTopLevelItem(deviceItem); foreach(const Partition* p, selectedDevice()->partitionTable().children()) { QTreeWidgetItem* item = createTreeWidgetItem(*p); foreach(const Partition* child, p->children()) { QTreeWidgetItem* childItem = createTreeWidgetItem(*child); item->addChild(childItem); } deviceItem->addChild(item); item->setExpanded(true); } treePartitions().setFirstItemColumnSpanned(deviceItem, true); deviceItem->setExpanded(true); deviceItem->setFlags(Qt::ItemIsEnabled); partTableWidget().update(); } void MainWindow::on_m_TreePartitions_currentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem*) { if (current) { const PartitionTreeWidgetItem* ptwItem = dynamic_cast(current); partTableWidget().setActivePartition(ptwItem ? ptwItem->partition() : NULL); } else partTableWidget().setActiveWidget(NULL); updateWindowTitle(); } void MainWindow::on_m_TreePartitions_itemActivated(QTreeWidgetItem* item) { // if the activated item is the device item, don't do anything if (item == treePartitions().topLevelItem(0)) return; actionCollection()->action("propertiesPartition")->trigger(); } Partition* MainWindow::selectedPartition() { if (selectedDevice() == NULL || partTableWidget().activeWidget() == NULL || partTableWidget().activeWidget()->partition() == NULL) return NULL; // The active partition we get from PartTableWidget is const; we need non-const. // So take the first sector and find the partition in the selected device's // partition table. const Partition* activePartition = partTableWidget().activeWidget()->partition(); PartitionTable& ptable = selectedDevice()->partitionTable(); return ptable.findPartitionBySector(activePartition->firstSector(), PartitionRole(PartitionRole::Any)); } Device* MainWindow::selectedDevice() { if (listDevices().selectedItems().size() != 1) return NULL; int idx = listDevices().row(listDevices().selectedItems()[0]); if (idx < 0 || idx >= operationStack().previewDevices().size()) return NULL; return operationStack().previewDevices()[idx]; } void MainWindow::on_m_PartTableWidget_itemSelectionChanged(PartWidget* item) { enableActions(); if (item == NULL) { if (selectedDevice()) infoPane().showDevice(*selectedDevice()); else infoPane().clear(); treePartitions().setCurrentItem(NULL); return; } const Partition* p = item->partition(); Q_ASSERT(p); QList findResult = treePartitions().findItems(p->deviceNode(), Qt::MatchFixedString | Qt::MatchRecursive, 0); for (int idx = 0; idx < findResult.size(); idx++) { const PartitionTreeWidgetItem* ptwItem = dynamic_cast(findResult[idx]); if (ptwItem && ptwItem->partition() == p) { treePartitions().setCurrentItem(findResult[idx]); break; } } infoPane().showPartition(*p); } void MainWindow::on_m_PartTableWidget_customContextMenuRequested(const QPoint& pos) { showPartitionContextMenu(partTableWidget().mapToGlobal(pos)); } void MainWindow::on_m_TreePartitions_customContextMenuRequested(const QPoint& pos) { showPartitionContextMenu(treePartitions().viewport()->mapToGlobal(pos)); } void MainWindow::showPartitionContextMenu(const QPoint& pos) { if (selectedPartition() == NULL) return; KMenu partitionMenu; partitionMenu.addAction(actionCollection()->action("newPartition")); partitionMenu.addAction(actionCollection()->action("resizePartition")); partitionMenu.addAction(actionCollection()->action("deletePartition")); partitionMenu.addSeparator(); partitionMenu.addAction(actionCollection()->action("copyPartition")); partitionMenu.addAction(actionCollection()->action("pastePartition")); partitionMenu.addSeparator(); partitionMenu.addAction(actionCollection()->action("mountPartition")); partitionMenu.addSeparator(); partitionMenu.addAction(actionCollection()->action("checkPartition")); partitionMenu.addSeparator(); partitionMenu.addAction(actionCollection()->action("propertiesPartition")); partitionMenu.exec(pos); statusBar()->clearMessage(); } void MainWindow::on_m_ListDevices_customContextMenuRequested(const QPoint& pos) { on_m_ListDevices_itemClicked(); KMenu deviceMenu; deviceMenu.addAction(actionCollection()->action("createNewPartitionTable")); deviceMenu.exec(listDevices().viewport()->mapToGlobal(pos)); statusBar()->clearMessage(); } void MainWindow::onPropertiesPartition() { if (selectedPartition()) { Q_ASSERT(selectedDevice()); PartPropsDialog dlg(this, *selectedDevice(), *selectedPartition()); if (dlg.exec() == KDialog::Accepted) { if (dlg.newFileSystemType() != selectedPartition()->fileSystem().type() || dlg.forceRecreate()) operationStack().push(new CreateFileSystemOperation(*selectedDevice(), *selectedPartition(), dlg.newFileSystemType())); if (dlg.newLabel() != selectedPartition()->fileSystem().label()) operationStack().push(new SetFileSystemLabelOperation(*selectedPartition(), dlg.newLabel())); if (dlg.newFlags() != selectedPartition()->activeFlags()) operationStack().push(new SetPartFlagsOperation(*selectedDevice(), *selectedPartition(), dlg.newFlags())); updatePartitions(); updateOperations(); updateStatusBar(); } } } void MainWindow::onMountPartition() { Partition* p = selectedPartition(); if (p && p->canMount()) { if (!p->mount()) KMessageBox::sorry(this, i18nc("@info", "The file system on partition %1 could not be mounted.", p->deviceNode()), i18nc("@title:window", "Could not mount file system.")); } else if (p && p->canUnmount()) { if (!p->unmount()) KMessageBox::sorry(this, i18nc("@info", "The file system on partition %1 could not be unmounted.", p->deviceNode()), i18nc("@title:window", "Could not unmount file system.")); } if (p->roles().has(PartitionRole::Logical)) { Partition* parent = dynamic_cast(p->parent()); Q_ASSERT(parent); if (parent != NULL) parent->checkChildrenMounted(); else kWarning() << "parent is null"; } enableActions(); updatePartitions(); } void MainWindow::onFinished() { partTableWidget().setUpdatesEnabled(true); #if !defined(NDEBUG) && 0 operationStack().clearOperations(); updatePartitions(); updateOperations(); enableActions(); #else devicesChanged(); #endif } static bool checkTooManyPartitions(QWidget* parent, const Device& d, const Partition& p) { if (p.roles().has(PartitionRole::Unallocated) && d.partitionTable().numPrimaries() >= d.partitionTable().maxPrimaries() && !p.roles().has(PartitionRole::Logical)) { KMessageBox::sorry(parent, i18nc("@info", "There are already %1 primary partitions on this device. This is the maximum number its partition table can handle." "You cannot create, paste or restore a primary partition on it before you delete an existing one.", d.partitionTable().numPrimaries()), i18nc("@title:window", "Too many primary partitions.")); return true; } return false; } void MainWindow::onNewPartition() { Q_ASSERT(selectedDevice()); Q_ASSERT(selectedPartition()); if (selectedDevice() == NULL || selectedPartition() == NULL) { kWarning() << "selected device: " << selectedDevice() << ", selected partition: " << selectedPartition(); return; } if (checkTooManyPartitions(this, *selectedDevice(), *selectedPartition())) return; Partition* newPartition = NewOperation::createNew(*selectedPartition()); NewDialog dlg(this, *selectedDevice(), *newPartition, selectedDevice()->partitionTable().childRoles(*selectedPartition())); if (dlg.exec() == KDialog::Accepted) { PartitionTable::snap(*selectedDevice(), *newPartition); operationStack().push(new NewOperation(*selectedDevice(), newPartition)); updatePartitions(); updateStatusBar(); updateOperations(); } else delete newPartition; } void MainWindow::onDeletePartition() { Q_ASSERT(selectedDevice()); Q_ASSERT(selectedPartition()); if (selectedDevice() == NULL || selectedPartition() == NULL) { kWarning() << "selected device: " << selectedDevice() << ", selected partition: " << selectedPartition(); return; } if (selectedPartition()->roles().has(PartitionRole::Logical)) { Q_ASSERT(selectedPartition()->parent()); if (selectedPartition()->parent() == NULL) { kWarning() << "parent of selected partition is null."; return; } if (selectedPartition()->parent()->highestMountedChild() > selectedPartition()->number()) { KMessageBox::sorry(this, i18nc("@info", "The partition %1 cannot currently be deleted because one or more partitions with higher logical numbers are still mounted." "Please unmount all partitions with higher logical numbers than %2 first.", selectedPartition()->deviceNode(), selectedPartition()->number()), i18nc("@title:window", "Cannot delete partition.")); return; } } if (clipboardPartition() == selectedPartition()) { if (KMessageBox::warningContinueCancel(this, i18nc("@info", "Do you really want to delete the partition that is currently in the clipboard? " "It will no longer be available for pasting after it has been deleted."), i18nc("@title:window", "Really delete partition in the clipboard?"), KGuiItem(i18nc("@action:button", "&Delete it")), KStandardGuiItem::cancel(), "reallyDeleteClipboardPartition") == KMessageBox::Cancel) return; setClipboardPartition(NULL); } operationStack().push(new DeleteOperation(*selectedDevice(), selectedPartition())); updatePartitions(); updateStatusBar(); updateOperations(); } void MainWindow::onResizePartition() { Q_ASSERT(selectedDevice()); Q_ASSERT(selectedPartition()); if (selectedDevice() == NULL || selectedPartition() == NULL) { kWarning() << "selected device: " << selectedDevice() << ", selected partition: " << selectedPartition(); return; } const qint64 freeBefore = selectedDevice()->partitionTable().freeSectorsBefore(*selectedPartition()); const qint64 freeAfter = selectedDevice()->partitionTable().freeSectorsAfter(*selectedPartition()); Partition resizedPartition(*selectedPartition()); ResizeDialog dlg(this, *selectedDevice(), resizedPartition, freeBefore, freeAfter); if (dlg.exec() == KDialog::Accepted && dlg.isModified()) { PartitionTable::snap(*selectedDevice(), resizedPartition, selectedPartition()); if (resizedPartition.firstSector() == selectedPartition()->firstSector() && resizedPartition.lastSector() == selectedPartition()->lastSector()) log(log::information) << i18nc("@info/plain", "Partition %1 has the same position and size after resize/move. Ignoring operation.", selectedPartition()->deviceNode()); else { operationStack().push(new ResizeOperation(*selectedDevice(), *selectedPartition(), resizedPartition.firstSector(), resizedPartition.lastSector())); updatePartitions(); updateStatusBar(); updateOperations(); } } } void MainWindow::onCopyPartition() { Q_ASSERT(selectedPartition()); if (selectedPartition() == NULL) { kWarning() << "selected partition: " << selectedPartition(); return; } setClipboardPartition(selectedPartition()); log() << i18nc("@info/plain", "Partition %1 has been copied to the clipboard.", selectedPartition()->deviceNode()); enableActions(); } void MainWindow::onPastePartition() { Q_ASSERT(selectedDevice()); Q_ASSERT(selectedPartition()); if (selectedDevice() == NULL || selectedPartition() == NULL) { kWarning() << "selected device: " << selectedDevice() << ", selected partition: " << selectedPartition(); return; } if (clipboardPartition() == NULL) { kWarning() << "no partition in the clipboard."; return; } if (checkTooManyPartitions(this, *selectedDevice(), *selectedPartition())) return; Device* dSource = operationStack().findDeviceForPartition(clipboardPartition()); Q_ASSERT(dSource); if (dSource == NULL) { kWarning() << "source partition is null."; return; } Partition* copiedPartition = CopyOperation::createCopy(*selectedPartition(), *clipboardPartition()); if (showInsertDialog(*copiedPartition, clipboardPartition()->length())) { operationStack().push(new CopyOperation(*selectedDevice(), copiedPartition, *dSource, clipboardPartition())); updatePartitions(); updateStatusBar(); updateOperations(); } else delete copiedPartition; } bool MainWindow::showInsertDialog(Partition& insertPartition, qint64 sourceLength) { Q_ASSERT(selectedDevice()); Q_ASSERT(selectedPartition()); if (selectedDevice() == NULL || selectedPartition() == NULL) { kWarning() << "selected device: " << selectedDevice() << ", selected partition: " << selectedPartition(); return false; } const bool overwrite = !selectedPartition()->roles().has(PartitionRole::Unallocated); // Make sure the inserted partition has the right parent and logical or primary set. Only then // can Device::snap() work correctly. selectedPartition()->parent()->reparent(insertPartition); if (!overwrite) { InsertDialog dlg(this, *selectedDevice(), insertPartition, *selectedPartition()); if (dlg.exec() != KDialog::Accepted) return false; PartitionTable::snap(*selectedDevice(), insertPartition, selectedPartition()); } if (insertPartition.length() < sourceLength) { if (overwrite) KMessageBox::error(this, i18nc("@info", "The selected partition is not large enough to hold the source partition or the backup file." "Pick another target or resize this partition so it is as large as the source."), i18nc("@title:window", "Target not large enough")); else KMessageBox::sorry(this, i18nc("@info", "It is not possible to create the target partition large enough to hold the source." "This may happen if not all partitions on a device start and end on cylinder boundaries " "or when copying a primary partition into an extended partition."), i18nc("@title:window", "Cannot create target partition.")); return false; } return true; } void MainWindow::onCreateNewPartitionTable() { Q_ASSERT(selectedDevice()); if (selectedDevice() == NULL) { kWarning() << "selected device is null."; return; } if (KMessageBox::warningContinueCancel(this, i18nc("@info", "Do you really want to create a new partition table on the following device?" "%1 (%2)" "This will destroy all data on the device.", selectedDevice()->deviceNode(), selectedDevice()->name()), i18nc("@title:window", "Destroy all data on device?"), KGuiItem(i18nc("@action:button", "&Create new partition table")), KStandardGuiItem::cancel()) == KMessageBox::Continue) { operationStack().push(new CreatePartitionTableOperation(*selectedDevice())); updateDevices(); updatePartitions(); updateStatusBar(); updateOperations(); enableActions(); } } void MainWindow::onRefreshDevices() { if (operationStack().size() == 0 || KMessageBox::warningContinueCancel(this, i18nc("@info", "Do you really want to rescan the devices?" "This will also clear the list of pending operations."), i18nc("@title:window", "Really rescan the devices?"), KGuiItem(i18nc("@action:button", "&Rescan devices")), KStandardGuiItem::cancel(), "reallyRescanDevices") == KMessageBox::Continue) { scanDevices(); } } void MainWindow::onUndoOperation() { log() << i18nc("@info/plain", "Undoing operation: %1", operationStack().operations().last()->description()); operationStack().pop(); updateDevices(); updatePartitions(); updateOperations(); updateStatusBar(); enableActions(); } void MainWindow::onClearAllOperations() { if (KMessageBox::warningContinueCancel(this, i18nc("@info", "Do you really want to clear the list of pending operations?"), i18nc("@title:window", "Clear pending operations?"), KGuiItem(i18nc("@action:button", "&Clear pending operations")), KStandardGuiItem::cancel(), "reallyClearPendingOperations") == KMessageBox::Continue) { log() << i18nc("@info/plain", "Clearing the list of pending operations."); operationStack().clearOperations(); updateDevices(); updatePartitions(); updateOperations(); updateStatusBar(); enableActions(); } } void MainWindow::onApplyAllOperations() { QStringList opList; foreach (const Operation* op, operationStack().operations()) opList.append(op->description()); if (KMessageBox::warningContinueCancelList(this, i18nc("@info", "Do you really want to apply the pending operations listed below?" "This will permanently modify your disks."), opList, i18nc("@title:window", "Apply pending operations?"), KGuiItem(i18nc("@action:button", "&Apply pending operations")), KStandardGuiItem::cancel()) == KMessageBox::Continue) { log() << i18nc("@info/plain", "Applying operations..."); progressDialog().show(); operationRunner().setReport(&progressDialog().report()); partTableWidget().setUpdatesEnabled(false); // Undo all operations so the runner has a defined starting point for (int i = operationStack().operations().size() - 1; i >= 0; i--) { operationStack().operations()[i]->undo(); operationStack().operations()[i]->setStatus(Operation::StatusNone); } updatePartitions(); operationRunner().start(); } } void MainWindow::onCheckPartition() { Q_ASSERT(selectedDevice()); Q_ASSERT(selectedPartition()); if (selectedDevice() == NULL || selectedPartition() == NULL) { kWarning() << "selected device: " << selectedDevice() << ", selected partition: " << selectedPartition(); return; } operationStack().push(new CheckOperation(*selectedDevice(), *selectedPartition())); updatePartitions(); updateStatusBar(); updateOperations(); } void MainWindow::onBackupPartition() { Q_ASSERT(selectedDevice()); Q_ASSERT(selectedPartition()); if (selectedDevice() == NULL || selectedPartition() == NULL) { kWarning() << "selected device: " << selectedDevice() << ", selected partition: " << selectedPartition(); return; } QString fileName = KFileDialog::getSaveFileName(KUrl("kfiledialog://backupPartition")); // QString fileName = "/tmp/backuptest.img"; if (fileName.isEmpty()) return; if (!QFile::exists(fileName) || KMessageBox::warningContinueCancel(this, i18nc("@info", "Do you want to overwrite the existing file %1?", fileName), i18nc("@title:window", "Overwrite existing file?"), KGuiItem(i18nc("@action:button", "&Overwrite file")), KStandardGuiItem::cancel()) == KMessageBox::Continue) { operationStack().push(new BackupOperation(*selectedDevice(), *selectedPartition(), fileName)); updatePartitions(); updateStatusBar(); updateOperations(); } } void MainWindow::onRestorePartition() { Q_ASSERT(selectedDevice()); Q_ASSERT(selectedPartition()); if (selectedDevice() == NULL || selectedPartition() == NULL) { kWarning() << "selected device: " << selectedDevice() << ", selected partition: " << selectedPartition(); return; } if (checkTooManyPartitions(this, *selectedDevice(), *selectedPartition())) return; QString fileName = KFileDialog::getOpenFileName(KUrl("kfiledialog://backupPartition")); // QString fileName = "/tmp/backuptest.img"; if (!fileName.isEmpty() && QFile::exists(fileName)) { Partition* restorePartition = RestoreOperation::createRestorePartition(*selectedDevice(), *selectedPartition()->parent(), selectedPartition()->firstSector(), fileName); if (restorePartition->length() > selectedPartition()->length()) { KMessageBox::error(this, i18nc("@info", "The file system in the image file %1 is too large to be restored to the selected partition.", fileName), i18nc("@title:window", "Not enough space to restore file system.")); delete restorePartition; return; } if (showInsertDialog(*restorePartition, restorePartition->length())) { operationStack().push(new RestoreOperation(*selectedDevice(), restorePartition, fileName)); updatePartitions(); updateStatusBar(); updateOperations(); } else delete restorePartition; } } void MainWindow::onFileSystemSupport() { FileSystemSupportDialog dlg(this); dlg.exec(); } void MainWindow::onNewLogMessage(log::Level logLevel, const QString& s) { static const char* icons[] = { "tools-report-bug", "dialog-information", "dialog-warning", "dialog-error" }; kDebug() << s; QTreeWidgetItem* item = new QTreeWidgetItem(); item->setIcon(0, SmallIcon(icons[logLevel])); item->setText(0, QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")); item->setText(1, s); treeLog().addTopLevelItem(item); for (int i = 0; i < treeLog().model()->columnCount(); i++) treeLog().resizeColumnToContents(i); treeLog().scrollToBottom(); }