diff --git a/src/kssld/kssld_adaptor.h b/src/kssld/kssld_adaptor.h index 0c98d007..337eb362 100644 --- a/src/kssld/kssld_adaptor.h +++ b/src/kssld/kssld_adaptor.h @@ -1,73 +1,73 @@ /* This file is part of the KDE libraries Copyright (C) 2007 Andreas Hartmetz This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KSSLD_ADAPTOR_H #define KSSLD_ADAPTOR_H #include #include #include #include #include "kssld_dbusmetatypes.h" class KSSLDAdaptor: public QDBusAbstractAdaptor { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.KSSLD") public: explicit KSSLDAdaptor(KSSLD *parent) : QDBusAbstractAdaptor(parent) { Q_ASSERT(parent); registerMetaTypesForKSSLD(); } private: inline KSSLD *p() { return static_cast(parent()); } public Q_SLOTS: inline Q_NOREPLY void setRule(const KSslCertificateRule &rule) { - return p()->setRule(rule); + p()->setRule(rule); } inline Q_NOREPLY void clearRule__rule(const KSslCertificateRule &rule) { - return p()->clearRule(rule); + p()->clearRule(rule); } inline Q_NOREPLY void clearRule__certHost(const QSslCertificate &cert, const QString &hostName) { - return p()->clearRule(cert, hostName); + p()->clearRule(cert, hostName); } inline KSslCertificateRule rule(const QSslCertificate &cert, const QString &hostName) { return p()->rule(cert, hostName); } }; #endif //KSSLD_ADAPTOR_H diff --git a/src/widgets/kacleditwidget.cpp b/src/widgets/kacleditwidget.cpp index 03398522..61656764 100644 --- a/src/widgets/kacleditwidget.cpp +++ b/src/widgets/kacleditwidget.cpp @@ -1,1190 +1,1190 @@ /*************************************************************************** * Copyright (C) 2005 by Sean Harmer * * 2005 - 2007 Till Adam * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library 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 "kacleditwidget.h" #include "kacleditwidget_p.h" #include "kio_widgets_debug.h" #if HAVE_POSIX_ACL #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if HAVE_ACL_LIBACL_H # include #endif extern "C" { #include #include } #include static struct { const char *label; const char *pixmapName; QPixmap *pixmap; } s_itemAttributes[] = { { I18N_NOOP("Owner"), "user-grey", nullptr }, { I18N_NOOP("Owning Group"), "group-grey", nullptr }, { I18N_NOOP("Others"), "others-grey", nullptr }, { I18N_NOOP("Mask"), "mask", nullptr }, { I18N_NOOP("Named User"), "user", nullptr }, { I18N_NOOP("Named Group"), "group", nullptr }, }; class KACLEditWidget::KACLEditWidgetPrivate { public: KACLEditWidgetPrivate() { } // slots void _k_slotUpdateButtons(); KACLListView *m_listView; QPushButton *m_AddBtn; QPushButton *m_EditBtn; QPushButton *m_DelBtn; }; KACLEditWidget::KACLEditWidget(QWidget *parent) : QWidget(parent), d(new KACLEditWidgetPrivate) { QHBoxLayout *hbox = new QHBoxLayout(this); hbox->setMargin(0); d->m_listView = new KACLListView(this); hbox->addWidget(d->m_listView); connect(d->m_listView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(_k_slotUpdateButtons())); QVBoxLayout *vbox = new QVBoxLayout(); hbox->addLayout(vbox); d->m_AddBtn = new QPushButton(i18n("Add Entry..."), this); vbox->addWidget(d->m_AddBtn); d->m_AddBtn->setObjectName(QStringLiteral("add_entry_button")); connect(d->m_AddBtn, SIGNAL(clicked()), d->m_listView, SLOT(slotAddEntry())); d->m_EditBtn = new QPushButton(i18n("Edit Entry..."), this); vbox->addWidget(d->m_EditBtn); d->m_EditBtn->setObjectName(QStringLiteral("edit_entry_button")); connect(d->m_EditBtn, SIGNAL(clicked()), d->m_listView, SLOT(slotEditEntry())); d->m_DelBtn = new QPushButton(i18n("Delete Entry"), this); vbox->addWidget(d->m_DelBtn); d->m_DelBtn->setObjectName(QStringLiteral("delete_entry_button")); connect(d->m_DelBtn, SIGNAL(clicked()), d->m_listView, SLOT(slotRemoveEntry())); vbox->addItem(new QSpacerItem(10, 10, QSizePolicy::Fixed, QSizePolicy::Expanding)); d->_k_slotUpdateButtons(); } KACLEditWidget::~KACLEditWidget() { delete d; } void KACLEditWidget::KACLEditWidgetPrivate::_k_slotUpdateButtons() { bool atLeastOneIsNotDeletable = false; bool atLeastOneIsNotAllowedToChangeType = false; int selectedCount = 0; QList selected = m_listView->selectedItems(); QListIterator it(selected); while (it.hasNext()) { KACLListViewItem *item = static_cast(it.next()); ++selectedCount; if (!item->isDeletable()) { atLeastOneIsNotDeletable = true; } if (!item->isAllowedToChangeType()) { atLeastOneIsNotAllowedToChangeType = true; } } m_EditBtn->setEnabled(selectedCount && !atLeastOneIsNotAllowedToChangeType); m_DelBtn->setEnabled(selectedCount && !atLeastOneIsNotDeletable); } KACL KACLEditWidget::getACL() const { return d->m_listView->getACL(); } KACL KACLEditWidget::getDefaultACL() const { return d->m_listView->getDefaultACL(); } void KACLEditWidget::setACL(const KACL &acl) { - return d->m_listView->setACL(acl); + d->m_listView->setACL(acl); } void KACLEditWidget::setDefaultACL(const KACL &acl) { - return d->m_listView->setDefaultACL(acl); + d->m_listView->setDefaultACL(acl); } void KACLEditWidget::setAllowDefaults(bool value) { d->m_listView->setAllowDefaults(value); } KACLListViewItem::KACLListViewItem(QTreeWidget *parent, KACLListView::EntryType _type, unsigned short _value, bool defaults, const QString &_qualifier) : QTreeWidgetItem(parent), type(_type), value(_value), isDefault(defaults), qualifier(_qualifier), isPartial(false) { m_pACLListView = qobject_cast(parent); repaint(); } KACLListViewItem::~ KACLListViewItem() { } QString KACLListViewItem::key() const { QString key; if (!isDefault) { key = 'A'; } else { key = 'B'; } switch (type) { case KACLListView::User: key += 'A'; break; case KACLListView::Group: key += 'B'; break; case KACLListView::Others: key += 'C'; break; case KACLListView::Mask: key += 'D'; break; case KACLListView::NamedUser: key += 'E' + text(1); break; case KACLListView::NamedGroup: key += 'F' + text(1); break; default: key += text(0); break; } return key; } bool KACLListViewItem::operator< (const QTreeWidgetItem &other) const { return key() < static_cast(other).key(); } #if 0 void KACLListViewItem::paintCell(QPainter *p, const QColorGroup &cg, int column, int width, int alignment) { if (isDefault) { setForeground(QColor(0, 0, 255)); } if (isPartial) { QFont font = p->font(); font.setItalic(true); setForeground(QColor(100, 100, 100)); p->setFont(font); } QTreeWidgetItem::paintCell(p, mycg, column, width, alignment); KACLListViewItem *below = 0; if (itemBelow()) { below = static_cast(itemBelow()); } const bool lastUser = type == KACLListView::NamedUser && below && below->type == KACLListView::NamedGroup; const bool lastNonDefault = !isDefault && below && below->isDefault; if (type == KACLListView::Mask || lastUser || lastNonDefault) { p->setPen(QPen(Qt::gray, 0, Qt::DotLine)); if (type == KACLListView::Mask) { p->drawLine(0, 0, width - 1, 0); } p->drawLine(0, height() - 1, width - 1, height() - 1); } } #endif void KACLListViewItem::updatePermPixmaps() { unsigned int partialPerms = value; if (value & ACL_READ) { setIcon(2, m_pACLListView->getYesPixmap()); } else if (partialPerms & ACL_READ) { setIcon(2, m_pACLListView->getYesPartialPixmap()); } else { setIcon(2, QIcon()); } if (value & ACL_WRITE) { setIcon(3, m_pACLListView->getYesPixmap()); } else if (partialPerms & ACL_WRITE) { setIcon(3, m_pACLListView->getYesPartialPixmap()); } else { setIcon(3, QIcon()); } if (value & ACL_EXECUTE) { setIcon(4, m_pACLListView->getYesPixmap()); } else if (partialPerms & ACL_EXECUTE) { setIcon(4, m_pACLListView->getYesPartialPixmap()); } else { setIcon(4, QIcon()); } } void KACLListViewItem::repaint() { int idx = 0; switch (type) { case KACLListView::User: idx = KACLListView::OWNER_IDX; break; case KACLListView::Group: idx = KACLListView::GROUP_IDX; break; case KACLListView::Others: idx = KACLListView::OTHERS_IDX; break; case KACLListView::Mask: idx = KACLListView::MASK_IDX; break; case KACLListView::NamedUser: idx = KACLListView::NAMED_USER_IDX; break; case KACLListView::NamedGroup: idx = KACLListView::NAMED_GROUP_IDX; break; default: idx = KACLListView::OWNER_IDX; break; } setText(0, i18n(s_itemAttributes[idx].label)); setIcon(0, *s_itemAttributes[idx].pixmap); if (isDefault) { setText(0, text(0) + i18n(" (Default)")); } setText(1, qualifier); // Set the pixmaps for which of the perms are set updatePermPixmaps(); } void KACLListViewItem::calcEffectiveRights() { QString strEffective = QStringLiteral("---"); // Do we need to worry about the mask entry? It applies to named users, // owning group, and named groups if (m_pACLListView->hasMaskEntry() && (type == KACLListView::NamedUser || type == KACLListView::Group || type == KACLListView::NamedGroup) && !isDefault) { strEffective[0] = (m_pACLListView->maskPermissions() & value & ACL_READ) ? 'r' : '-'; strEffective[1] = (m_pACLListView->maskPermissions() & value & ACL_WRITE) ? 'w' : '-'; strEffective[2] = (m_pACLListView->maskPermissions() & value & ACL_EXECUTE) ? 'x' : '-'; /* // What about any partial perms? if ( maskPerms & partialPerms & ACL_READ || // Partial perms on entry maskPartialPerms & perms & ACL_READ || // Partial perms on mask maskPartialPerms & partialPerms & ACL_READ ) // Partial perms on mask and entry strEffective[0] = 'R'; if ( maskPerms & partialPerms & ACL_WRITE || // Partial perms on entry maskPartialPerms & perms & ACL_WRITE || // Partial perms on mask maskPartialPerms & partialPerms & ACL_WRITE ) // Partial perms on mask and entry strEffective[1] = 'W'; if ( maskPerms & partialPerms & ACL_EXECUTE || // Partial perms on entry maskPartialPerms & perms & ACL_EXECUTE || // Partial perms on mask maskPartialPerms & partialPerms & ACL_EXECUTE ) // Partial perms on mask and entry strEffective[2] = 'X'; */ } else { // No, the effective value are just the value in this entry strEffective[0] = (value & ACL_READ) ? 'r' : '-'; strEffective[1] = (value & ACL_WRITE) ? 'w' : '-'; strEffective[2] = (value & ACL_EXECUTE) ? 'x' : '-'; /* // What about any partial perms? if ( partialPerms & ACL_READ ) strEffective[0] = 'R'; if ( partialPerms & ACL_WRITE ) strEffective[1] = 'W'; if ( partialPerms & ACL_EXECUTE ) strEffective[2] = 'X'; */ } setText(5, strEffective); } bool KACLListViewItem::isDeletable() const { bool isMaskAndDeletable = false; if (type == KACLListView::Mask) { if (!isDefault && m_pACLListView->maskCanBeDeleted()) { isMaskAndDeletable = true; } else if (isDefault && m_pACLListView->defaultMaskCanBeDeleted()) { isMaskAndDeletable = true; } } return type != KACLListView::User && type != KACLListView::Group && type != KACLListView::Others && (type != KACLListView::Mask || isMaskAndDeletable); } bool KACLListViewItem::isAllowedToChangeType() const { return type != KACLListView::User && type != KACLListView::Group && type != KACLListView::Others && type != KACLListView::Mask; } void KACLListViewItem::togglePerm(acl_perm_t perm) { value ^= perm; // Toggle the perm if (type == KACLListView::Mask && !isDefault) { m_pACLListView->setMaskPermissions(value); } calcEffectiveRights(); updatePermPixmaps(); /* // If the perm is in the partial perms then remove it. i.e. Once // a user changes a partial perm it then applies to all selected files. if ( m_pEntry->m_partialPerms & perm ) m_pEntry->m_partialPerms ^= perm; m_pEntry->setPartialEntry( false ); // Make sure that all entries have their effective rights calculated if // we are changing the ACL_MASK entry. if ( type == Mask ) { m_pACLListView->setMaskPartialPermissions( m_pEntry->m_partialPerms ); m_pACLListView->setMaskPermissions( value ); m_pACLListView->calculateEffectiveRights(); } */ } EditACLEntryDialog::EditACLEntryDialog(KACLListView *listView, KACLListViewItem *item, const QStringList &users, const QStringList &groups, const QStringList &defaultUsers, const QStringList &defaultGroups, int allowedTypes, int allowedDefaultTypes, bool allowDefaults) : QDialog(listView), m_listView(listView), m_item(item), m_users(users), m_groups(groups), m_defaultUsers(defaultUsers), m_defaultGroups(defaultGroups), m_allowedTypes(allowedTypes), m_allowedDefaultTypes(allowedDefaultTypes), m_defaultCB(nullptr) { setObjectName(QStringLiteral("edit_entry_dialog")); setModal(true); setWindowTitle(i18n("Edit ACL Entry")); QVBoxLayout *mainLayout = new QVBoxLayout; setLayout(mainLayout); QGroupBox *gb = new QGroupBox(i18n("Entry Type"), this); QVBoxLayout *gbLayout = new QVBoxLayout(gb); m_buttonGroup = new QButtonGroup(this); if (allowDefaults) { m_defaultCB = new QCheckBox(i18n("Default for new files in this folder"), this); m_defaultCB->setObjectName(QStringLiteral("defaultCB")); mainLayout->addWidget(m_defaultCB); connect(m_defaultCB, SIGNAL(toggled(bool)), this, SLOT(slotUpdateAllowedUsersAndGroups())); connect(m_defaultCB, SIGNAL(toggled(bool)), this, SLOT(slotUpdateAllowedTypes())); } QRadioButton *ownerType = new QRadioButton(i18n("Owner"), gb); ownerType->setObjectName(QStringLiteral("ownerType")); gbLayout->addWidget(ownerType); m_buttonGroup->addButton(ownerType); m_buttonIds.insert(ownerType, KACLListView::User); QRadioButton *owningGroupType = new QRadioButton(i18n("Owning Group"), gb); owningGroupType->setObjectName(QStringLiteral("owningGroupType")); gbLayout->addWidget(owningGroupType); m_buttonGroup->addButton(owningGroupType); m_buttonIds.insert(owningGroupType, KACLListView::Group); QRadioButton *othersType = new QRadioButton(i18n("Others"), gb); othersType->setObjectName(QStringLiteral("othersType")); gbLayout->addWidget(othersType); m_buttonGroup->addButton(othersType); m_buttonIds.insert(othersType, KACLListView::Others); QRadioButton *maskType = new QRadioButton(i18n("Mask"), gb); maskType->setObjectName(QStringLiteral("maskType")); gbLayout->addWidget(maskType); m_buttonGroup->addButton(maskType); m_buttonIds.insert(maskType, KACLListView::Mask); QRadioButton *namedUserType = new QRadioButton(i18n("Named user"), gb); namedUserType->setObjectName(QStringLiteral("namesUserType")); gbLayout->addWidget(namedUserType); m_buttonGroup->addButton(namedUserType); m_buttonIds.insert(namedUserType, KACLListView::NamedUser); QRadioButton *namedGroupType = new QRadioButton(i18n("Named group"), gb); namedGroupType->setObjectName(QStringLiteral("namedGroupType")); gbLayout->addWidget(namedGroupType); m_buttonGroup->addButton(namedGroupType); m_buttonIds.insert(namedGroupType, KACLListView::NamedGroup); mainLayout->addWidget(gb); connect(m_buttonGroup, SIGNAL(buttonClicked(QAbstractButton*)), this, SLOT(slotSelectionChanged(QAbstractButton*))); m_widgetStack = new QStackedWidget(this); mainLayout->addWidget(m_widgetStack); // users box QWidget *usersBox = new QWidget(m_widgetStack); QHBoxLayout *usersLayout = new QHBoxLayout(usersBox); usersBox->setLayout(usersLayout); m_widgetStack->addWidget(usersBox); QLabel *usersLabel = new QLabel(i18n("User: "), usersBox); m_usersCombo = new KComboBox(usersBox); m_usersCombo->setEditable(false); m_usersCombo->setObjectName(QStringLiteral("users")); usersLabel->setBuddy(m_usersCombo); usersLayout->addWidget(usersLabel); usersLayout->addWidget(m_usersCombo); // groups box QWidget *groupsBox = new QWidget(m_widgetStack); QHBoxLayout *groupsLayout = new QHBoxLayout(usersBox); groupsBox->setLayout(groupsLayout); m_widgetStack->addWidget(groupsBox); QLabel *groupsLabel = new QLabel(i18n("Group: "), groupsBox); m_groupsCombo = new KComboBox(groupsBox); m_groupsCombo->setEditable(false); m_groupsCombo->setObjectName(QStringLiteral("groups")); groupsLabel->setBuddy(m_groupsCombo); groupsLayout->addWidget(groupsLabel); groupsLayout->addWidget(m_groupsCombo); if (m_item) { m_buttonIds.key(m_item->type)->setChecked(true); if (m_defaultCB) { m_defaultCB->setChecked(m_item->isDefault); } slotUpdateAllowedTypes(); slotSelectionChanged(m_buttonIds.key(m_item->type)); slotUpdateAllowedUsersAndGroups(); if (m_item->type == KACLListView::NamedUser) { m_usersCombo->setItemText(m_usersCombo->currentIndex(), m_item->qualifier); } else if (m_item->type == KACLListView::NamedGroup) { m_groupsCombo->setItemText(m_groupsCombo->currentIndex(), m_item->qualifier); } } else { // new entry, preselect "named user", arguably the most common one m_buttonIds.key(KACLListView::NamedUser)->setChecked(true); slotUpdateAllowedTypes(); slotSelectionChanged(m_buttonIds.key(KACLListView::NamedUser)); slotUpdateAllowedUsersAndGroups(); } QDialogButtonBox *buttonBox = new QDialogButtonBox(this); buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(buttonBox, SIGNAL(accepted()), this, SLOT(slotOk())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); mainLayout->addWidget(buttonBox); adjustSize(); } void EditACLEntryDialog::slotUpdateAllowedTypes() { int allowedTypes = m_allowedTypes; if (m_defaultCB && m_defaultCB->isChecked()) { allowedTypes = m_allowedDefaultTypes; } for (int i = 1; i < KACLListView::AllTypes; i = i * 2) { if (allowedTypes & i) { m_buttonIds.key(i)->show(); } else { m_buttonIds.key(i)->hide(); } } } void EditACLEntryDialog::slotUpdateAllowedUsersAndGroups() { const QString oldUser = m_usersCombo->currentText(); const QString oldGroup = m_groupsCombo->currentText(); m_usersCombo->clear(); m_groupsCombo->clear(); if (m_defaultCB && m_defaultCB->isChecked()) { m_usersCombo->addItems(m_defaultUsers); if (m_defaultUsers.contains(oldUser)) { m_usersCombo->setItemText(m_usersCombo->currentIndex(), oldUser); } m_groupsCombo->addItems(m_defaultGroups); if (m_defaultGroups.contains(oldGroup)) { m_groupsCombo->setItemText(m_groupsCombo->currentIndex(), oldGroup); } } else { m_usersCombo->addItems(m_users); if (m_users.contains(oldUser)) { m_usersCombo->setItemText(m_usersCombo->currentIndex(), oldUser); } m_groupsCombo->addItems(m_groups); if (m_groups.contains(oldGroup)) { m_groupsCombo->setItemText(m_groupsCombo->currentIndex(), oldGroup); } } } void EditACLEntryDialog::slotOk() { KACLListView::EntryType type = static_cast(m_buttonIds[m_buttonGroup->checkedButton()]); qCWarning(KIO_WIDGETS) << "Type 2: " << type; QString qualifier; if (type == KACLListView::NamedUser) { qualifier = m_usersCombo->currentText(); } if (type == KACLListView::NamedGroup) { qualifier = m_groupsCombo->currentText(); } if (!m_item) { m_item = new KACLListViewItem(m_listView, type, ACL_READ | ACL_WRITE | ACL_EXECUTE, false, qualifier); } else { m_item->type = type; m_item->qualifier = qualifier; } if (m_defaultCB) { m_item->isDefault = m_defaultCB->isChecked(); } m_item->repaint(); QDialog::accept(); } void EditACLEntryDialog::slotSelectionChanged(QAbstractButton *button) { switch (m_buttonIds[ button ]) { case KACLListView::User: case KACLListView::Group: case KACLListView::Others: case KACLListView::Mask: m_widgetStack->setEnabled(false); break; case KACLListView::NamedUser: m_widgetStack->setEnabled(true); m_widgetStack->setCurrentIndex(0 /* User */); break; case KACLListView::NamedGroup: m_widgetStack->setEnabled(true); m_widgetStack->setCurrentIndex(1 /* Group */); break; default: break; } } KACLListView::KACLListView(QWidget *parent) : QTreeWidget(parent), m_hasMask(false), m_allowDefaults(false) { // Add the columns setColumnCount(6); QStringList headers; headers << i18n("Type"); headers << i18n("Name"); headers << i18nc("read permission", "r"); headers << i18nc("write permission", "w"); headers << i18nc("execute permission", "x"); headers << i18n("Effective"); setHeaderLabels(headers); setSortingEnabled(false); setSelectionMode(QAbstractItemView::ExtendedSelection); header()->setSectionResizeMode(QHeaderView::ResizeToContents); setRootIsDecorated(false); // Load the avatars for (int i = 0; i < LAST_IDX; ++i) { s_itemAttributes[i].pixmap = new QPixmap(QStringLiteral(":/images/%1").arg(s_itemAttributes[i].pixmapName)); } m_yesPixmap = new QPixmap(QStringLiteral(":/images/yes.png")); m_yesPartialPixmap = new QPixmap(QStringLiteral(":/images/yespartial.png")); // fill the lists of all legal users and groups struct passwd *user = nullptr; setpwent(); while ((user = getpwent()) != nullptr) { m_allUsers << QString::fromLatin1(user->pw_name); } endpwent(); struct group *gr = nullptr; setgrent(); while ((gr = getgrent()) != nullptr) { m_allGroups << QString::fromLatin1(gr->gr_name); } endgrent(); m_allUsers.sort(); m_allGroups.sort(); connect(this, SIGNAL(itemClicked(QTreeWidgetItem*,int)), this, SLOT(slotItemClicked(QTreeWidgetItem*,int))); connect(this, &KACLListView::itemDoubleClicked, this, &KACLListView::slotItemDoubleClicked); } KACLListView::~KACLListView() { for (int i = 0; i < LAST_IDX; ++i) { delete s_itemAttributes[i].pixmap; } delete m_yesPixmap; delete m_yesPartialPixmap; } QStringList KACLListView::allowedUsers(bool defaults, KACLListViewItem *allowedItem) { QStringList allowedUsers = m_allUsers; QTreeWidgetItemIterator it(this); while (*it) { const KACLListViewItem *item = static_cast(*it); ++it; if (item->type != NamedUser || item->isDefault != defaults) { continue; } if (allowedItem && item == allowedItem && allowedItem->isDefault == defaults) { continue; } allowedUsers.removeAll(item->qualifier); } return allowedUsers; } QStringList KACLListView::allowedGroups(bool defaults, KACLListViewItem *allowedItem) { QStringList allowedGroups = m_allGroups; QTreeWidgetItemIterator it(this); while (*it) { const KACLListViewItem *item = static_cast(*it); ++it; if (item->type != NamedGroup || item->isDefault != defaults) { continue; } if (allowedItem && item == allowedItem && allowedItem->isDefault == defaults) { continue; } allowedGroups.removeAll(item->qualifier); } return allowedGroups; } void KACLListView::fillItemsFromACL(const KACL &pACL, bool defaults) { // clear out old entries of that ilk QTreeWidgetItemIterator it(this); while (KACLListViewItem *item = static_cast(*it)) { ++it; if (item->isDefault == defaults) { delete item; } } new KACLListViewItem(this, User, pACL.ownerPermissions(), defaults); new KACLListViewItem(this, Group, pACL.owningGroupPermissions(), defaults); new KACLListViewItem(this, Others, pACL.othersPermissions(), defaults); bool hasMask = false; unsigned short mask = pACL.maskPermissions(hasMask); if (hasMask) { new KACLListViewItem(this, Mask, mask, defaults); } // read all named user entries const ACLUserPermissionsList &userList = pACL.allUserPermissions(); ACLUserPermissionsConstIterator itu = userList.begin(); while (itu != userList.end()) { new KACLListViewItem(this, NamedUser, (*itu).second, defaults, (*itu).first); ++itu; } // and now all named groups const ACLUserPermissionsList &groupList = pACL.allGroupPermissions(); ACLUserPermissionsConstIterator itg = groupList.begin(); while (itg != groupList.end()) { new KACLListViewItem(this, NamedGroup, (*itg).second, defaults, (*itg).first); ++itg; } } void KACLListView::setACL(const KACL &acl) { if (!acl.isValid()) { return; } // Remove any entries left over from displaying a previous ACL m_ACL = acl; fillItemsFromACL(m_ACL); m_mask = acl.maskPermissions(m_hasMask); calculateEffectiveRights(); } void KACLListView::setDefaultACL(const KACL &acl) { if (!acl.isValid()) { return; } m_defaultACL = acl; fillItemsFromACL(m_defaultACL, true); calculateEffectiveRights(); } KACL KACLListView::itemsToACL(bool defaults) const { KACL newACL(0); bool atLeastOneEntry = false; ACLUserPermissionsList users; ACLGroupPermissionsList groups; QTreeWidgetItemIterator it(const_cast(this)); while (QTreeWidgetItem *qlvi = *it) { ++it; const KACLListViewItem *item = static_cast(qlvi); if (item->isDefault != defaults) { continue; } atLeastOneEntry = true; switch (item->type) { case User: newACL.setOwnerPermissions(item->value); break; case Group: newACL.setOwningGroupPermissions(item->value); break; case Others: newACL.setOthersPermissions(item->value); break; case Mask: newACL.setMaskPermissions(item->value); break; case NamedUser: users.append(qMakePair(item->text(1), item->value)); break; case NamedGroup: groups.append(qMakePair(item->text(1), item->value)); break; default: break; } } if (atLeastOneEntry) { newACL.setAllUserPermissions(users); newACL.setAllGroupPermissions(groups); if (newACL.isValid()) { return newACL; } } return KACL(); } KACL KACLListView::getACL() { return itemsToACL(false); } KACL KACLListView::getDefaultACL() { return itemsToACL(true); } void KACLListView::contentsMousePressEvent(QMouseEvent * /*e*/) { /* QTreeWidgetItem *clickedItem = itemAt( e->pos() ); if ( !clickedItem ) return; // if the click is on an as yet unselected item, select it first if ( !clickedItem->isSelected() ) QAbstractItemView::contentsMousePressEvent( e ); if ( !currentItem() ) return; int column = header()->sectionAt( e->x() ); acl_perm_t perm; switch ( column ) { case 2: perm = ACL_READ; break; case 3: perm = ACL_WRITE; break; case 4: perm = ACL_EXECUTE; break; default: return QTreeWidget::contentsMousePressEvent( e ); } KACLListViewItem* referenceItem = static_cast( clickedItem ); unsigned short referenceHadItSet = referenceItem->value & perm; QTreeWidgetItemIterator it( this ); while ( KACLListViewItem* item = static_cast( *it ) ) { ++it; if ( !item->isSelected() ) continue; // toggle those with the same value as the clicked item, leave the others if ( referenceHadItSet == ( item->value & perm ) ) item->togglePerm( perm ); } */ } void KACLListView::slotItemClicked(QTreeWidgetItem *pItem, int col) { if (!pItem) { return; } QTreeWidgetItemIterator it(this); while (KACLListViewItem *item = static_cast(*it)) { ++it; if (!item->isSelected()) { continue; } switch (col) { case 2: item->togglePerm(ACL_READ); break; case 3: item->togglePerm(ACL_WRITE); break; case 4: item->togglePerm(ACL_EXECUTE); break; default: ; // Do nothing } } /* // Has the user changed one of the required entries in a default ACL? if ( m_pACL->aclType() == ACL_TYPE_DEFAULT && ( col == 2 || col == 3 || col == 4 ) && ( pACLItem->entryType() == ACL_USER_OBJ || pACLItem->entryType() == ACL_GROUP_OBJ || pACLItem->entryType() == ACL_OTHER ) ) { // Mark the required entries as no longer being partial entries. // That is, they will get applied to all selected directories. KACLListViewItem* pUserObj = findACLEntryByType( this, ACL_USER_OBJ ); pUserObj->entry()->setPartialEntry( false ); KACLListViewItem* pGroupObj = findACLEntryByType( this, ACL_GROUP_OBJ ); pGroupObj->entry()->setPartialEntry( false ); KACLListViewItem* pOther = findACLEntryByType( this, ACL_OTHER ); pOther->entry()->setPartialEntry( false ); update(); } */ } void KACLListView::slotItemDoubleClicked(QTreeWidgetItem *item, int column) { if (!item) { return; } // avoid conflict with clicking to toggle permission if (column >= 2 && column <= 4) { return; } KACLListViewItem *aclListItem = static_cast(item); if (!aclListItem->isAllowedToChangeType()) { return; } setCurrentItem(item); slotEditEntry(); } void KACLListView::calculateEffectiveRights() { QTreeWidgetItemIterator it(this); KACLListViewItem *pItem; while ((pItem = dynamic_cast(*it)) != nullptr) { ++it; pItem->calcEffectiveRights(); } } unsigned short KACLListView::maskPermissions() const { return m_mask; } void KACLListView::setMaskPermissions(unsigned short maskPerms) { m_mask = maskPerms; calculateEffectiveRights(); } acl_perm_t KACLListView::maskPartialPermissions() const { // return m_pMaskEntry->m_partialPerms; return 0; } void KACLListView::setMaskPartialPermissions(acl_perm_t /*maskPartialPerms*/) { //m_pMaskEntry->m_partialPerms = maskPartialPerms; calculateEffectiveRights(); } bool KACLListView::hasDefaultEntries() const { QTreeWidgetItemIterator it(const_cast(this)); while (*it) { const KACLListViewItem *item = static_cast(*it); ++it; if (item->isDefault) { return true; } } return false; } const KACLListViewItem *KACLListView::findDefaultItemByType(EntryType type) const { return findItemByType(type, true); } const KACLListViewItem *KACLListView::findItemByType(EntryType type, bool defaults) const { QTreeWidgetItemIterator it(const_cast(this)); while (*it) { const KACLListViewItem *item = static_cast(*it); ++it; if (item->isDefault == defaults && item->type == type) { return item; } } return nullptr; } unsigned short KACLListView::calculateMaskValue(bool defaults) const { // KACL auto-adds the relevant maks entries, so we can simply query bool dummy; return itemsToACL(defaults).maskPermissions(dummy); } void KACLListView::slotAddEntry() { int allowedTypes = NamedUser | NamedGroup; if (!m_hasMask) { allowedTypes |= Mask; } int allowedDefaultTypes = NamedUser | NamedGroup; if (!findDefaultItemByType(Mask)) { allowedDefaultTypes |= Mask; } if (!hasDefaultEntries()) { allowedDefaultTypes |= User | Group; } EditACLEntryDialog dlg(this, nullptr, allowedUsers(false), allowedGroups(false), allowedUsers(true), allowedGroups(true), allowedTypes, allowedDefaultTypes, m_allowDefaults); dlg.exec(); KACLListViewItem *item = dlg.item(); if (!item) { return; // canceled } if (item->type == Mask && !item->isDefault) { m_hasMask = true; m_mask = item->value; } if (item->isDefault && !hasDefaultEntries()) { // first default entry, fill in what is needed if (item->type != User) { unsigned short v = findDefaultItemByType(User)->value; new KACLListViewItem(this, User, v, true); } if (item->type != Group) { unsigned short v = findDefaultItemByType(Group)->value; new KACLListViewItem(this, Group, v, true); } if (item->type != Others) { unsigned short v = findDefaultItemByType(Others)->value; new KACLListViewItem(this, Others, v, true); } } const KACLListViewItem *defaultMaskItem = findDefaultItemByType(Mask); if (item->isDefault && !defaultMaskItem) { unsigned short v = calculateMaskValue(true); new KACLListViewItem(this, Mask, v, true); } if (!item->isDefault && !m_hasMask && (item->type == Group || item->type == NamedUser || item->type == NamedGroup)) { // auto-add a mask entry unsigned short v = calculateMaskValue(false); new KACLListViewItem(this, Mask, v, false); m_hasMask = true; m_mask = v; } calculateEffectiveRights(); sortItems(sortColumn(), Qt::AscendingOrder); setCurrentItem(item); // QTreeWidget doesn't seem to emit, in this case, and we need to update // the buttons... if (topLevelItemCount() == 1) { emit currentItemChanged(item, item); } } void KACLListView::slotEditEntry() { QTreeWidgetItem *current = currentItem(); if (!current) { return; } KACLListViewItem *item = static_cast(current); int allowedTypes = item->type | NamedUser | NamedGroup; bool itemWasMask = item->type == Mask; if (!m_hasMask || itemWasMask) { allowedTypes |= Mask; } int allowedDefaultTypes = item->type | NamedUser | NamedGroup; if (!findDefaultItemByType(Mask)) { allowedDefaultTypes |= Mask; } if (!hasDefaultEntries()) { allowedDefaultTypes |= User | Group; } EditACLEntryDialog dlg(this, item, allowedUsers(false, item), allowedGroups(false, item), allowedUsers(true, item), allowedGroups(true, item), allowedTypes, allowedDefaultTypes, m_allowDefaults); dlg.exec(); if (itemWasMask && item->type != Mask) { m_hasMask = false; m_mask = 0; } if (!itemWasMask && item->type == Mask) { m_mask = item->value; m_hasMask = true; } calculateEffectiveRights(); sortItems(sortColumn(), Qt::AscendingOrder); } void KACLListView::slotRemoveEntry() { QTreeWidgetItemIterator it(this, QTreeWidgetItemIterator::Selected); while (*it) { KACLListViewItem *item = static_cast(*it); ++it; /* First check if it's a mask entry and if so, make sure that there is * either no name user or group entry, which means the mask can be * removed, or don't remove it, but reset it. That is allowed. */ if (item->type == Mask) { bool itemWasDefault = item->isDefault; if (!itemWasDefault && maskCanBeDeleted()) { m_hasMask = false; m_mask = 0; delete item; } else if (itemWasDefault && defaultMaskCanBeDeleted()) { delete item; } else { item->value = 0; item->repaint(); } if (!itemWasDefault) { calculateEffectiveRights(); } } else { // for the base permissions, disable them, which is what libacl does if (!item->isDefault && (item->type == User || item->type == Group || item->type == Others)) { item->value = 0; item->repaint(); } else { delete item; } } } } bool KACLListView::maskCanBeDeleted() const { return !findItemByType(NamedUser) && !findItemByType(NamedGroup); } bool KACLListView::defaultMaskCanBeDeleted() const { return !findDefaultItemByType(NamedUser) && !findDefaultItemByType(NamedGroup); } #include "moc_kacleditwidget.cpp" #include "moc_kacleditwidget_p.cpp" #endif diff --git a/src/widgets/kdirmodel.cpp b/src/widgets/kdirmodel.cpp index 37581fbf..3fb59c65 100644 --- a/src/widgets/kdirmodel.cpp +++ b/src/widgets/kdirmodel.cpp @@ -1,1292 +1,1292 @@ /* This file is part of the KDE project Copyright (C) 2006 David Faure This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kdirmodel.h" #include "kdirlister.h" #include "kfileitem.h" #include "kio_widgets_debug.h" #include #include #include #include #include "joburlcache_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN #include #endif class KDirModelNode; class KDirModelDirNode; static QUrl cleanupUrl(const QUrl &url) { QUrl u = url; u.setPath(QDir::cleanPath(u.path())); // remove double slashes in the path, simplify "foo/." to "foo/", etc. u = u.adjusted(QUrl::StripTrailingSlash); // KDirLister does this too, so we remove the slash before comparing with the root node url. u.setQuery(QString()); u.setFragment(QString()); return u; } // We create our own tree behind the scenes to have fast lookup from an item to its parent, // and also to get the children of an item fast. class KDirModelNode { public: KDirModelNode(KDirModelDirNode *parent, const KFileItem &item) : m_item(item), m_parent(parent), m_preview() { } virtual ~KDirModelNode() { // Required, code will delete ptrs to this or a subclass. } // m_item is KFileItem() for the root item const KFileItem &item() const { return m_item; } void setItem(const KFileItem &item) { m_item = item; } KDirModelDirNode *parent() const { return m_parent; } // linear search int rowNumber() const; // O(n) QIcon preview() const { return m_preview; } void setPreview(const QPixmap &pix) { m_preview = QIcon(); m_preview.addPixmap(pix); } void setPreview(const QIcon &icn) { m_preview = icn; } private: KFileItem m_item; KDirModelDirNode *const m_parent; QIcon m_preview; }; // Specialization for directory nodes class KDirModelDirNode : public KDirModelNode { public: KDirModelDirNode(KDirModelDirNode *parent, const KFileItem &item) : KDirModelNode(parent, item), m_childNodes(), m_childCount(KDirModel::ChildCountUnknown), m_populated(false) {} virtual ~KDirModelDirNode() override { qDeleteAll(m_childNodes); } QList m_childNodes; // owns the nodes // If we listed the directory, the child count is known. Otherwise it can be set via setChildCount. int childCount() const { return m_childNodes.isEmpty() ? m_childCount : m_childNodes.count(); } void setChildCount(int count) { m_childCount = count; } bool isPopulated() const { return m_populated; } void setPopulated(bool populated) { m_populated = populated; } bool isSlow() const { return item().isSlow(); } // For removing all child urls from the global hash. void collectAllChildUrls(QList &urls) const { Q_FOREACH (KDirModelNode *node, m_childNodes) { const KFileItem &item = node->item(); urls.append(cleanupUrl(item.url())); if (item.isDir()) { static_cast(node)->collectAllChildUrls(urls); } } } private: int m_childCount: 31; bool m_populated: 1; }; int KDirModelNode::rowNumber() const { if (!m_parent) { return 0; } return m_parent->m_childNodes.indexOf(const_cast(this)); } //// class KDirModelPrivate { public: KDirModelPrivate(KDirModel *model) : q(model), m_dirLister(nullptr), m_rootNode(new KDirModelDirNode(nullptr, KFileItem())), m_dropsAllowed(KDirModel::NoDrops), m_jobTransfersVisible(false) { } ~KDirModelPrivate() { delete m_rootNode; } void _k_slotNewItems(const QUrl &directoryUrl, const KFileItemList &); void _k_slotDeleteItems(const KFileItemList &); void _k_slotRefreshItems(const QList > &); void _k_slotClear(); void _k_slotRedirection(const QUrl &oldUrl, const QUrl &newUrl); void _k_slotJobUrlsChanged(const QStringList &urlList); void clear() { delete m_rootNode; m_rootNode = new KDirModelDirNode(nullptr, KFileItem()); } // Emit expand for each parent and then return the // last known parent if there is no node for this url KDirModelNode *expandAllParentsUntil(const QUrl &url) const; // Return the node for a given url, using the hash. KDirModelNode *nodeForUrl(const QUrl &url) const; KDirModelNode *nodeForIndex(const QModelIndex &index) const; QModelIndex indexForNode(KDirModelNode *node, int rowNumber = -1 /*unknown*/) const; bool isDir(KDirModelNode *node) const { return (node == m_rootNode) || node->item().isDir(); } QUrl urlForNode(KDirModelNode *node) const { /** * Queries and fragments are removed from the URL, so that the URL of * child items really starts with the URL of the parent. * * For instance ksvn+http://url?rev=100 is the parent for ksvn+http://url/file?rev=100 * so we have to remove the query in both to be able to compare the URLs */ QUrl url(node == m_rootNode ? m_dirLister->url() : node->item().url()); if (url.hasQuery() || url.hasFragment()) { // avoid detach if not necessary. url.setQuery(QString()); url.setFragment(QString()); // kill ref (#171117) } return url; } void removeFromNodeHash(KDirModelNode *node, const QUrl &url); void clearAllPreviews(KDirModelDirNode *node); #ifndef NDEBUG void dump(); #endif KDirModel *q; KDirLister *m_dirLister; KDirModelDirNode *m_rootNode; KDirModel::DropsAllowed m_dropsAllowed; bool m_jobTransfersVisible; // key = current known parent node (always a KDirModelDirNode but KDirModelNode is more convenient), // value = final url[s] being fetched QMap > m_urlsBeingFetched; QHash m_nodeHash; // global node hash: url -> node QStringList m_allCurrentDestUrls; //list of all dest urls that have jobs on them (e.g. copy, download) }; KDirModelNode *KDirModelPrivate::nodeForUrl(const QUrl &_url) const // O(1), well, O(length of url as a string) { QUrl url = cleanupUrl(_url); if (url == urlForNode(m_rootNode)) { return m_rootNode; } return m_nodeHash.value(url); } void KDirModelPrivate::removeFromNodeHash(KDirModelNode *node, const QUrl &url) { if (node->item().isDir()) { QList urls; static_cast(node)->collectAllChildUrls(urls); Q_FOREACH (const QUrl &u, urls) { m_nodeHash.remove(u); } } m_nodeHash.remove(cleanupUrl(url)); } KDirModelNode *KDirModelPrivate::expandAllParentsUntil(const QUrl &_url) const // O(depth) { QUrl url = cleanupUrl(_url); //qDebug() << url; QUrl nodeUrl = urlForNode(m_rootNode); if (url == nodeUrl) { return m_rootNode; } // Protocol mismatch? Don't even start comparing paths then. #171721 if (url.scheme() != nodeUrl.scheme()) { return nullptr; } const QString pathStr = url.path(); // no trailing slash KDirModelDirNode *dirNode = m_rootNode; if (!pathStr.startsWith(nodeUrl.path())) { return nullptr; } for (;;) { QString nodePath = nodeUrl.path(); if (!nodePath.endsWith('/')) { nodePath += '/'; } if (!pathStr.startsWith(nodePath)) { qCWarning(KIO_WIDGETS) << "The kioslave for" << url.scheme() << "violates the hierarchy structure:" << "I arrived at node" << nodePath << ", but" << pathStr << "does not start with that path."; return nullptr; } // E.g. pathStr is /a/b/c and nodePath is /a/. We want to find the node with url /a/b const int nextSlash = pathStr.indexOf('/', nodePath.length()); const QString newPath = pathStr.left(nextSlash); // works even if nextSlash==-1 nodeUrl.setPath(newPath); nodeUrl = nodeUrl.adjusted(QUrl::StripTrailingSlash); // #172508 KDirModelNode *node = nodeForUrl(nodeUrl); if (!node) { //qDebug() << "child equal or starting with" << url << "not found"; // return last parent found: return dirNode; } emit q->expand(indexForNode(node)); //qDebug() << " nodeUrl=" << nodeUrl; if (nodeUrl == url) { //qDebug() << "Found node" << node << "for" << url; return node; } //qDebug() << "going into" << node->item().url(); Q_ASSERT(isDir(node)); dirNode = static_cast(node); } // NOTREACHED //return 0; } #ifndef NDEBUG void KDirModelPrivate::dump() { qCDebug(KIO_WIDGETS) << "Dumping contents of KDirModel" << q << "dirLister url:" << m_dirLister->url(); QHashIterator it(m_nodeHash); while (it.hasNext()) { it.next(); qDebug(KIO_WIDGETS) << it.key() << it.value(); } } #endif // node -> index. If rowNumber is set (or node is root): O(1). Otherwise: O(n). QModelIndex KDirModelPrivate::indexForNode(KDirModelNode *node, int rowNumber) const { if (node == m_rootNode) { return QModelIndex(); } Q_ASSERT(node->parent()); return q->createIndex(rowNumber == -1 ? node->rowNumber() : rowNumber, 0, node); } // index -> node. O(1) KDirModelNode *KDirModelPrivate::nodeForIndex(const QModelIndex &index) const { return index.isValid() ? static_cast(index.internalPointer()) : m_rootNode; } /* * This model wraps the data held by KDirLister. * * The internal pointer of the QModelIndex for a given file is the node for that file in our own tree. * E.g. index(2,0) returns a QModelIndex with row=2 internalPointer= * * Invalid parent index means root of the tree, m_rootNode */ #ifndef NDEBUG static QString debugIndex(const QModelIndex &index) { QString str; if (!index.isValid()) { str = QStringLiteral("[invalid index, i.e. root]"); } else { KDirModelNode *node = static_cast(index.internalPointer()); str = "[index for " + node->item().url().toString(); if (index.column() > 0) { str += ", column " + QString::number(index.column()); } str += ']'; } return str; } #endif KDirModel::KDirModel(QObject *parent) : QAbstractItemModel(parent), d(new KDirModelPrivate(this)) { setDirLister(new KDirLister(this)); } KDirModel::~KDirModel() { delete d; } void KDirModel::setDirLister(KDirLister *dirLister) { if (d->m_dirLister) { d->clear(); delete d->m_dirLister; } d->m_dirLister = dirLister; d->m_dirLister->setParent(this); connect(d->m_dirLister, &KCoreDirLister::itemsAdded, this, [this](const QUrl &dirUrl, const KFileItemList &items){d->_k_slotNewItems(dirUrl, items);} ); connect(d->m_dirLister, &KCoreDirLister::itemsDeleted, this, [this](const KFileItemList &items){d->_k_slotDeleteItems(items);} ); connect(d->m_dirLister, &KCoreDirLister::refreshItems, this, [this](const QList > &items){d->_k_slotRefreshItems(items);} ); connect(d->m_dirLister, QOverload<>::of(&KCoreDirLister::clear), this, [this](){d->_k_slotClear();} ); connect(d->m_dirLister, QOverload::of(&KCoreDirLister::redirection), this, [this](const QUrl &oldUrl, const QUrl &newUrl){d->_k_slotRedirection(oldUrl, newUrl);} ); } KDirLister *KDirModel::dirLister() const { return d->m_dirLister; } void KDirModelPrivate::_k_slotNewItems(const QUrl &directoryUrl, const KFileItemList &items) { //qDebug() << "directoryUrl=" << directoryUrl; KDirModelNode *result = nodeForUrl(directoryUrl); // O(depth) // If the directory containing the items wasn't found, then we have a big problem. // Are you calling KDirLister::openUrl(url,true,false)? Please use expandToUrl() instead. if (!result) { qCWarning(KIO_WIDGETS) << "Items emitted in directory" << directoryUrl << "but that directory isn't in KDirModel!" << "Root directory:" << urlForNode(m_rootNode); Q_FOREACH (const KFileItem &item, items) { qDebug() << "Item:" << item.url(); } #ifndef NDEBUG dump(); #endif Q_ASSERT(result); } Q_ASSERT(isDir(result)); KDirModelDirNode *dirNode = static_cast(result); const QModelIndex index = indexForNode(dirNode); // O(n) const int newItemsCount = items.count(); const int newRowCount = dirNode->m_childNodes.count() + newItemsCount; #if 0 #ifndef NDEBUG // debugIndex only defined in debug mode //qDebug() << items.count() << "in" << directoryUrl << "index=" << debugIndex(index) << "newRowCount=" << newRowCount; #endif #endif q->beginInsertRows(index, newRowCount - newItemsCount, newRowCount - 1); // parent, first, last const QList urlsBeingFetched = m_urlsBeingFetched.value(dirNode); //qDebug() << "urlsBeingFetched for dir" << dirNode << directoryUrl << ":" << urlsBeingFetched; QList emitExpandFor; KFileItemList::const_iterator it = items.begin(); KFileItemList::const_iterator end = items.end(); for (; it != end; ++it) { const bool isDir = it->isDir(); KDirModelNode *node = isDir ? new KDirModelDirNode(dirNode, *it) : new KDirModelNode(dirNode, *it); #ifndef NDEBUG // Test code for possible duplication of items in the childnodes list, // not sure if/how it ever happened. //if (dirNode->m_childNodes.count() && // dirNode->m_childNodes.last()->item().name() == (*it).name()) { // qCWarning(KIO_WIDGETS) << "Already having" << (*it).name() << "in" << directoryUrl // << "url=" << dirNode->m_childNodes.last()->item().url(); // abort(); //} #endif dirNode->m_childNodes.append(node); const QUrl url = it->url(); m_nodeHash.insert(cleanupUrl(url), node); //qDebug() << url; if (!urlsBeingFetched.isEmpty()) { const QUrl dirUrl(url); foreach (const QUrl &urlFetched, urlsBeingFetched) { if (dirUrl.matches(urlFetched, QUrl::StripTrailingSlash) || dirUrl.isParentOf(urlFetched)) { //qDebug() << "Listing found" << dirUrl.url() << "which is a parent of fetched url" << urlFetched; const QModelIndex parentIndex = indexForNode(node, dirNode->m_childNodes.count() - 1); Q_ASSERT(parentIndex.isValid()); emitExpandFor.append(parentIndex); if (isDir && dirUrl != urlFetched) { q->fetchMore(parentIndex); m_urlsBeingFetched[node].append(urlFetched); } } } } } m_urlsBeingFetched.remove(dirNode); q->endInsertRows(); // Emit expand signal after rowsInserted signal has been emitted, // so that any proxy model will have updated its mapping already Q_FOREACH (const QModelIndex &idx, emitExpandFor) { emit q->expand(idx); } } void KDirModelPrivate::_k_slotDeleteItems(const KFileItemList &items) { //qDebug() << items.count(); // I assume all items are from the same directory. // From KDirLister's code, this should be the case, except maybe emitChanges? const KFileItem item = items.first(); Q_ASSERT(!item.isNull()); QUrl url = item.url(); KDirModelNode *node = nodeForUrl(url); // O(depth) if (!node) { qCWarning(KIO_WIDGETS) << "No node found for item that was just removed:" << url; return; } KDirModelDirNode *dirNode = node->parent(); if (!dirNode) { return; } QModelIndex parentIndex = indexForNode(dirNode); // O(n) // Short path for deleting a single item if (items.count() == 1) { const int r = node->rowNumber(); q->beginRemoveRows(parentIndex, r, r); removeFromNodeHash(node, url); delete dirNode->m_childNodes.takeAt(r); q->endRemoveRows(); return; } // We need to make lists of consecutive row numbers, for the beginRemoveRows call. // Let's use a bit array where each bit represents a given child node. const int childCount = dirNode->m_childNodes.count(); QBitArray rowNumbers(childCount, false); Q_FOREACH (const KFileItem &item, items) { if (!node) { // don't lookup the first item twice url = item.url(); node = nodeForUrl(url); if (!node) { qCWarning(KIO_WIDGETS) << "No node found for item that was just removed:" << url; continue; } if (!node->parent()) { // The root node has been deleted, but it was not first in the list 'items'. // see https://bugs.kde.org/show_bug.cgi?id=196695 return; } } rowNumbers.setBit(node->rowNumber(), 1); // O(n) removeFromNodeHash(node, url); node = nullptr; } int start = -1; int end = -1; bool lastVal = false; // Start from the end, otherwise all the row numbers are offset while we go for (int i = childCount - 1; i >= 0; --i) { const bool val = rowNumbers.testBit(i); if (!lastVal && val) { end = i; //qDebug() << "end=" << end; } if ((lastVal && !val) || (i == 0 && val)) { start = val ? i : i + 1; //qDebug() << "beginRemoveRows" << start << end; q->beginRemoveRows(parentIndex, start, end); for (int r = end; r >= start; --r) { // reverse because takeAt changes indexes ;) //qDebug() << "Removing from m_childNodes at" << r; delete dirNode->m_childNodes.takeAt(r); } q->endRemoveRows(); } lastVal = val; } } void KDirModelPrivate::_k_slotRefreshItems(const QList > &items) { QModelIndex topLeft, bottomRight; // Solution 1: we could emit dataChanged for one row (if items.size()==1) or all rows // Solution 2: more fine-grained, actually figure out the beginning and end rows. for (QList >::const_iterator fit = items.begin(), fend = items.end(); fit != fend; ++fit) { Q_ASSERT(!fit->first.isNull()); Q_ASSERT(!fit->second.isNull()); const QUrl oldUrl = fit->first.url(); const QUrl newUrl = fit->second.url(); KDirModelNode *node = nodeForUrl(oldUrl); // O(n); maybe we could look up to the parent only once //qDebug() << "in model for" << m_dirLister->url() << ":" << oldUrl << "->" << newUrl << "node=" << node; if (!node) { // not found [can happen when renaming a dir, redirection was emitted already] continue; } if (node != m_rootNode) { // we never set an item in the rootnode, we use m_dirLister->rootItem instead. bool hasNewNode = false; // A file became directory (well, it was overwritten) if (fit->first.isDir() != fit->second.isDir()) { //qDebug() << "DIR/FILE STATUS CHANGE"; const int r = node->rowNumber(); removeFromNodeHash(node, oldUrl); KDirModelDirNode *dirNode = node->parent(); delete dirNode->m_childNodes.takeAt(r); // i.e. "delete node" node = fit->second.isDir() ? new KDirModelDirNode(dirNode, fit->second) : new KDirModelNode(dirNode, fit->second); dirNode->m_childNodes.insert(r, node); // same position! hasNewNode = true; } else { node->setItem(fit->second); } if (oldUrl != newUrl || hasNewNode) { // What if a renamed dir had children? -> kdirlister takes care of emitting for each item //qDebug() << "Renaming" << oldUrl << "to" << newUrl << "in node hash"; m_nodeHash.remove(cleanupUrl(oldUrl)); m_nodeHash.insert(cleanupUrl(newUrl), node); } // Mimetype changed -> forget cached icon (e.g. from "cut", #164185 comment #13) if (fit->first.determineMimeType().name() != fit->second.determineMimeType().name()) { node->setPreview(QIcon()); } const QModelIndex index = indexForNode(node); if (!topLeft.isValid() || index.row() < topLeft.row()) { topLeft = index; } if (!bottomRight.isValid() || index.row() > bottomRight.row()) { bottomRight = index; } } } #ifndef NDEBUG // debugIndex only defined in debug mode //qDebug() << "dataChanged(" << debugIndex(topLeft) << " - " << debugIndex(bottomRight); Q_UNUSED(debugIndex(QModelIndex())); // fix compiler warning #endif bottomRight = bottomRight.sibling(bottomRight.row(), q->columnCount(QModelIndex()) - 1); emit q->dataChanged(topLeft, bottomRight); } // Called when a kioslave redirects (e.g. smb:/Workgroup -> smb://workgroup) // and when renaming a directory. void KDirModelPrivate::_k_slotRedirection(const QUrl &oldUrl, const QUrl &newUrl) { KDirModelNode *node = nodeForUrl(oldUrl); if (!node) { return; } m_nodeHash.remove(cleanupUrl(oldUrl)); m_nodeHash.insert(cleanupUrl(newUrl), node); // Ensure the node's URL is updated. In case of a listjob redirection // we won't get a refreshItem, and in case of renaming a directory // we'll get it too late (so the hash won't find the old url anymore). KFileItem item = node->item(); if (!item.isNull()) { // null if root item, #180156 item.setUrl(newUrl); node->setItem(item); } // The items inside the renamed directory have been handled before, // KDirLister took care of emitting refreshItem for each of them. } void KDirModelPrivate::_k_slotClear() { const int numRows = m_rootNode->m_childNodes.count(); if (numRows > 0) { q->beginRemoveRows(QModelIndex(), 0, numRows - 1); q->endRemoveRows(); } m_nodeHash.clear(); //emit layoutAboutToBeChanged(); clear(); //emit layoutChanged(); } void KDirModelPrivate::_k_slotJobUrlsChanged(const QStringList &urlList) { QStringList dirtyUrls; std::set_symmetric_difference(urlList.begin(), urlList.end(), m_allCurrentDestUrls.constBegin(), m_allCurrentDestUrls.constEnd(), std::back_inserter(dirtyUrls)); m_allCurrentDestUrls = urlList; for (const QString &dirtyUrl : qAsConst(dirtyUrls)) { if (KDirModelNode *node = nodeForUrl(QUrl(dirtyUrl))) { const QModelIndex idx = indexForNode(node); emit q->dataChanged(idx, idx, {KDirModel::HasJobRole}); } } } void KDirModelPrivate::clearAllPreviews(KDirModelDirNode *dirNode) { const int numRows = dirNode->m_childNodes.count(); if (numRows > 0) { KDirModelNode *lastNode = nullptr; for (KDirModelNode *node : qAsConst(dirNode->m_childNodes)) { node->setPreview(QIcon()); //node->setPreview(QIcon::fromTheme(node->item().iconName())); if (isDir(node)) { // recurse into child dirs clearAllPreviews(static_cast(node)); } lastNode = node; } emit q->dataChanged(indexForNode(dirNode->m_childNodes.at(0), 0), // O(1) indexForNode(lastNode, numRows - 1)); // O(1) } } void KDirModel::clearAllPreviews() { d->clearAllPreviews(d->m_rootNode); } void KDirModel::itemChanged(const QModelIndex &index) { // This method is really a itemMimeTypeChanged(), it's mostly called by KFilePreviewGenerator. // When the mimetype is determined, clear the old "preview" (could be // mimetype dependent like when cutting files, #164185) KDirModelNode *node = d->nodeForIndex(index); if (node) { node->setPreview(QIcon()); } #ifndef NDEBUG // debugIndex only defined in debug mode //qDebug() << "dataChanged(" << debugIndex(index); #endif emit dataChanged(index, index); } int KDirModel::columnCount(const QModelIndex &) const { return ColumnCount; } QVariant KDirModel::data(const QModelIndex &index, int role) const { if (index.isValid()) { KDirModelNode *node = static_cast(index.internalPointer()); const KFileItem &item(node->item()); switch (role) { case Qt::DisplayRole: switch (index.column()) { case Name: return item.text(); case Size: return KIO::convertSize(item.size()); // size formatted as QString case ModifiedTime: { QDateTime dt = item.time(KFileItem::ModificationTime); return dt.toString(Qt::SystemLocaleShortDate); } case Permissions: return item.permissionsString(); case Owner: return item.user(); case Group: return item.group(); case Type: return item.mimeComment(); } break; case Qt::EditRole: switch (index.column()) { case Name: return item.text(); } break; case Qt::DecorationRole: if (index.column() == Name) { if (!node->preview().isNull()) { //qDebug() << item->url() << " preview found"; return node->preview(); } Q_ASSERT(!item.isNull()); //qDebug() << item->url() << " overlays=" << item->overlays(); return KDE::icon(item.iconName(), item.overlays()); } break; case Qt::TextAlignmentRole: if (index.column() == Size) { // use a right alignment for L2R and R2L languages const Qt::Alignment alignment = Qt::AlignRight | Qt::AlignVCenter; return int(alignment); } break; case Qt::ToolTipRole: return item.text(); case FileItemRole: return QVariant::fromValue(item); case ChildCountRole: if (!item.isDir()) { return ChildCountUnknown; } else { KDirModelDirNode *dirNode = static_cast(node); int count = dirNode->childCount(); if (count == ChildCountUnknown && item.isReadable() && !dirNode->isSlow()) { const QString path = item.localPath(); if (!path.isEmpty()) { // slow // QDir dir(path); // count = dir.entryList(QDir::AllEntries|QDir::NoDotAndDotDot|QDir::System).count(); #ifdef Q_OS_WIN QString s = path + QLatin1String("\\*.*"); s.replace('/', '\\'); count = 0; WIN32_FIND_DATA findData; HANDLE hFile = FindFirstFile((LPWSTR)s.utf16(), &findData); if (hFile != INVALID_HANDLE_VALUE) { do { if (!(findData.cFileName[0] == '.' && findData.cFileName[1] == '\0') && !(findData.cFileName[0] == '.' && findData.cFileName[1] == '.' && findData.cFileName[2] == '\0')) { ++count; } } while (FindNextFile(hFile, &findData) != 0); FindClose(hFile); } #else DIR *dir = QT_OPENDIR(QFile::encodeName(path)); if (dir) { count = 0; QT_DIRENT *dirEntry = nullptr; while ((dirEntry = QT_READDIR(dir))) { if (dirEntry->d_name[0] == '.') { if (dirEntry->d_name[1] == '\0') { // skip "." continue; } if (dirEntry->d_name[1] == '.' && dirEntry->d_name[2] == '\0') { // skip ".." continue; } } ++count; } QT_CLOSEDIR(dir); } #endif //qDebug() << "child count for " << path << ":" << count; dirNode->setChildCount(count); } } return count; } case HasJobRole: if (d->m_jobTransfersVisible && d->m_allCurrentDestUrls.isEmpty() == false) { KDirModelNode *node = d->nodeForIndex(index); const QString url = node->item().url().toString(); //return whether or not there are job dest urls visible in the view, so the delegate knows which ones to paint. return QVariant(d->m_allCurrentDestUrls.contains(url)); } } } return QVariant(); } void KDirModel::sort(int column, Qt::SortOrder order) { // Not implemented - we should probably use QSortFilterProxyModel instead. - return QAbstractItemModel::sort(column, order); + QAbstractItemModel::sort(column, order); } bool KDirModel::setData(const QModelIndex &index, const QVariant &value, int role) { switch (role) { case Qt::EditRole: if (index.column() == Name && value.type() == QVariant::String) { Q_ASSERT(index.isValid()); KDirModelNode *node = static_cast(index.internalPointer()); const KFileItem &item = node->item(); const QString newName = value.toString(); if (newName.isEmpty() || newName == item.text() || (newName == QLatin1String(".")) || (newName == QLatin1String(".."))) { return true; } QUrl newUrl = item.url().adjusted(QUrl::RemoveFilename); newUrl.setPath(newUrl.path() + KIO::encodeFileName(newName)); KIO::Job *job = KIO::rename(item.url(), newUrl, item.url().isLocalFile() ? KIO::HideProgressInfo : KIO::DefaultFlags); job->uiDelegate()->setAutoErrorHandlingEnabled(true); // undo handling KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Rename, QList() << item.url(), newUrl, job); return true; } break; case Qt::DecorationRole: if (index.column() == Name) { Q_ASSERT(index.isValid()); // Set new pixmap - e.g. preview KDirModelNode *node = static_cast(index.internalPointer()); //qDebug() << "setting icon for " << node->item()->url(); Q_ASSERT(node); if (value.type() == QVariant::Icon) { const QIcon icon(qvariant_cast(value)); node->setPreview(icon); } else if (value.type() == QVariant::Pixmap) { node->setPreview(qvariant_cast(value)); } emit dataChanged(index, index); return true; } break; default: break; } return false; } int KDirModel::rowCount(const QModelIndex &parent) const { KDirModelNode *node = d->nodeForIndex(parent); if (!node || !d->isDir(node)) { // #176555 return 0; } KDirModelDirNode *parentNode = static_cast(node); Q_ASSERT(parentNode); const int count = parentNode->m_childNodes.count(); #if 0 QStringList filenames; for (int i = 0; i < count; ++i) { filenames << d->urlForNode(parentNode->m_childNodes.at(i)).fileName(); } //qDebug() << "rowCount for " << d->urlForNode(parentNode) << ": " << count << filenames; #endif return count; } QModelIndex KDirModel::parent(const QModelIndex &index) const { if (!index.isValid()) { return QModelIndex(); } KDirModelNode *childNode = static_cast(index.internalPointer()); Q_ASSERT(childNode); KDirModelNode *parentNode = childNode->parent(); Q_ASSERT(parentNode); return d->indexForNode(parentNode); // O(n) } // Reimplemented to avoid the default implementation which calls parent // (O(n) for finding the parent's row number for nothing). This implementation is O(1). QModelIndex KDirModel::sibling(int row, int column, const QModelIndex &index) const { if (!index.isValid()) { return QModelIndex(); } KDirModelNode *oldChildNode = static_cast(index.internalPointer()); Q_ASSERT(oldChildNode); KDirModelNode *parentNode = oldChildNode->parent(); Q_ASSERT(parentNode); Q_ASSERT(d->isDir(parentNode)); KDirModelNode *childNode = static_cast(parentNode)->m_childNodes.value(row); // O(1) if (childNode) { return createIndex(row, column, childNode); } return QModelIndex(); } void KDirModel::requestSequenceIcon(const QModelIndex &index, int sequenceIndex) { emit needSequenceIcon(index, sequenceIndex); } void KDirModel::setJobTransfersVisible(bool value) { if (value) { d->m_jobTransfersVisible = true; connect(&JobUrlCache::instance(), SIGNAL(jobUrlsChanged(QStringList)), this, SLOT(_k_slotJobUrlsChanged(QStringList)), Qt::UniqueConnection); JobUrlCache::instance().requestJobUrlsChanged(); } else { disconnect(this, SLOT(_k_slotJobUrlsChanged(QStringList))); } } bool KDirModel::jobTransfersVisible() const { return d->m_jobTransfersVisible; } QList KDirModel::simplifiedUrlList(const QList &urls) { if (!urls.count()) { return urls; } QList ret(urls); std::sort(ret.begin(), ret.end()); QList::iterator it = ret.begin(); QUrl url = *it; ++it; while (it != ret.end()) { if (url.isParentOf(*it) || url == *it) { it = ret.erase(it); } else { url = *it; ++it; } } return ret; } QStringList KDirModel::mimeTypes() const { return KUrlMimeData::mimeDataTypes(); } QMimeData *KDirModel::mimeData(const QModelIndexList &indexes) const { QList urls, mostLocalUrls; bool canUseMostLocalUrls = true; foreach (const QModelIndex &index, indexes) { const KFileItem &item = d->nodeForIndex(index)->item(); urls << item.url(); bool isLocal; mostLocalUrls << item.mostLocalUrl(isLocal); if (!isLocal) { canUseMostLocalUrls = false; } } QMimeData *data = new QMimeData(); const bool different = canUseMostLocalUrls && (mostLocalUrls != urls); urls = simplifiedUrlList(urls); if (different) { mostLocalUrls = simplifiedUrlList(mostLocalUrls); KUrlMimeData::setUrls(urls, mostLocalUrls, data); } else { data->setUrls(urls); } return data; } // Public API; not much point in calling it internally KFileItem KDirModel::itemForIndex(const QModelIndex &index) const { if (!index.isValid()) { return d->m_dirLister->rootItem(); } else { return static_cast(index.internalPointer())->item(); } } #ifndef KIOWIDGETS_NO_DEPRECATED QModelIndex KDirModel::indexForItem(const KFileItem *item) const { // Note that we can only use the URL here, not the pointer. // KFileItems can be copied. return indexForUrl(item->url()); // O(n) } #endif QModelIndex KDirModel::indexForItem(const KFileItem &item) const { // Note that we can only use the URL here, not the pointer. // KFileItems can be copied. return indexForUrl(item.url()); // O(n) } // url -> index. O(n) QModelIndex KDirModel::indexForUrl(const QUrl &url) const { KDirModelNode *node = d->nodeForUrl(url); // O(depth) if (!node) { //qDebug() << url << "not found"; return QModelIndex(); } return d->indexForNode(node); // O(n) } QModelIndex KDirModel::index(int row, int column, const QModelIndex &parent) const { KDirModelNode *parentNode = d->nodeForIndex(parent); // O(1) Q_ASSERT(parentNode); if (d->isDir(parentNode)) { KDirModelNode *childNode = static_cast(parentNode)->m_childNodes.value(row); // O(1) if (childNode) { return createIndex(row, column, childNode); } } return QModelIndex(); } QVariant KDirModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal) { switch (role) { case Qt::DisplayRole: switch (section) { case Name: return i18nc("@title:column", "Name"); case Size: return i18nc("@title:column", "Size"); case ModifiedTime: return i18nc("@title:column", "Date"); case Permissions: return i18nc("@title:column", "Permissions"); case Owner: return i18nc("@title:column", "Owner"); case Group: return i18nc("@title:column", "Group"); case Type: return i18nc("@title:column", "Type"); } } } return QVariant(); } bool KDirModel::hasChildren(const QModelIndex &parent) const { if (!parent.isValid()) { return true; } const KFileItem &parentItem = static_cast(parent.internalPointer())->item(); Q_ASSERT(!parentItem.isNull()); return parentItem.isDir(); } Qt::ItemFlags KDirModel::flags(const QModelIndex &index) const { Qt::ItemFlags f = Qt::ItemIsEnabled; if (index.column() == Name) { f |= Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled; } // Allow dropping onto this item? if (d->m_dropsAllowed != NoDrops) { if (!index.isValid()) { if (d->m_dropsAllowed & DropOnDirectory) { f |= Qt::ItemIsDropEnabled; } } else { KFileItem item = itemForIndex(index); if (item.isNull()) { qCWarning(KIO_WIDGETS) << "Invalid item returned for index"; } else if (item.isDir()) { if (d->m_dropsAllowed & DropOnDirectory) { f |= Qt::ItemIsDropEnabled; } } else { // regular file item if (d->m_dropsAllowed & DropOnAnyFile) { f |= Qt::ItemIsDropEnabled; } else if (d->m_dropsAllowed & DropOnLocalExecutable) { if (!item.localPath().isEmpty()) { // Desktop file? if (item.determineMimeType().inherits(QStringLiteral("application/x-desktop"))) { f |= Qt::ItemIsDropEnabled; } // Executable, shell script ... ? else if (QFileInfo(item.localPath()).isExecutable()) { f |= Qt::ItemIsDropEnabled; } } } } } } return f; } bool KDirModel::canFetchMore(const QModelIndex &parent) const { if (!parent.isValid()) { return false; } // We now have a bool KDirModelNode::m_populated, // to avoid calling fetchMore more than once on empty dirs. // But this wastes memory, and how often does someone open and re-open an empty dir in a treeview? // Maybe we can ask KDirLister "have you listed already"? (to discuss with M. Brade) KDirModelNode *node = static_cast(parent.internalPointer()); const KFileItem &item = node->item(); return item.isDir() && !static_cast(node)->isPopulated() && static_cast(node)->m_childNodes.isEmpty(); } void KDirModel::fetchMore(const QModelIndex &parent) { if (!parent.isValid()) { return; } KDirModelNode *parentNode = static_cast(parent.internalPointer()); KFileItem parentItem = parentNode->item(); Q_ASSERT(!parentItem.isNull()); if (!parentItem.isDir()) { return; } KDirModelDirNode *dirNode = static_cast(parentNode); if (dirNode->isPopulated()) { return; } dirNode->setPopulated(true); const QUrl parentUrl = parentItem.url(); d->m_dirLister->openUrl(parentUrl, KDirLister::Keep); } bool KDirModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { // Not sure we want to implement any drop handling at this level, // but for sure the default QAbstractItemModel implementation makes no sense for a dir model. Q_UNUSED(data); Q_UNUSED(action); Q_UNUSED(row); Q_UNUSED(column); Q_UNUSED(parent); return false; } void KDirModel::setDropsAllowed(DropsAllowed dropsAllowed) { d->m_dropsAllowed = dropsAllowed; } void KDirModel::expandToUrl(const QUrl &url) { // emit expand for each parent and return last parent KDirModelNode *result = d->expandAllParentsUntil(url); // O(depth) //qDebug() << url << result; if (!result) { // doesn't seem related to our base url? return; } if (!(result->item().isNull()) && result->item().url() == url) { // We have it already, nothing to do //qDebug() << "have it already item=" <item()*/; return; } d->m_urlsBeingFetched[result].append(url); if (result == d->m_rootNode) { //qDebug() << "Remembering to emit expand after listing the root url"; // the root is fetched by default, so it must be currently being fetched return; } //qDebug() << "Remembering to emit expand after listing" << result->item().url(); // start a new fetch to look for the next level down the URL const QModelIndex parentIndex = d->indexForNode(result); // O(n) Q_ASSERT(parentIndex.isValid()); fetchMore(parentIndex); } bool KDirModel::insertRows(int, int, const QModelIndex &) { return false; } bool KDirModel::insertColumns(int, int, const QModelIndex &) { return false; } bool KDirModel::removeRows(int, int, const QModelIndex &) { return false; } bool KDirModel::removeColumns(int, int, const QModelIndex &) { return false; } #include "moc_kdirmodel.cpp"