not quite so much needs to be delayed to the init() function
[personal-kdebase.git] / workspace / kcontrol / kfontinst / kcmfontinst / DuplicatesDialog.cpp
blob862ccd3d90adead2822aaea0bee8629223e6b827
1 /*
2 * KFontInst - KDE Font Installer
4 * Copyright 2003-2007 Craig Drummond <craig@kde.org>
6 * ----
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; see the file COPYING. If not, write to
20 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
21 * Boston, MA 02110-1301, USA.
24 #include "../config-fontinst.h"
25 #include "DuplicatesDialog.h"
26 #include "Misc.h"
27 #include "Fc.h"
28 #include "JobRunner.h"
29 #include "FontList.h"
30 #include <KDE/KGlobal>
31 #include <KDE/KIconLoader>
32 #include <KDE/KLocale>
33 #include <KDE/KMessageBox>
34 #include <KDE/SuProcess>
35 #include <KDE/KFileItem>
36 #include <KDE/KPropertiesDialog>
37 #include <KDE/KShell>
38 #include <QtGui/QLabel>
39 #include <QtCore/QTimer>
40 #include <QtGui/QGridLayout>
41 #include <QtCore/QDir>
42 #include <QtCore/QFileInfoList>
43 #include <QtCore/QFileInfo>
44 #include <QtGui/QHeaderView>
45 #include <QtCore/QDateTime>
46 #include <QtGui/QMenu>
47 #include <QtGui/QContextMenuEvent>
48 #include <QtGui/QAction>
49 #include <QtGui/QApplication>
50 #include <QtGui/QDesktopWidget>
51 #include <QtCore/QProcess>
52 #if defined USE_POLICYKIT && USE_POLICYKIT==1
53 #include <QtDBus/QDBusInterface>
54 #endif
56 using namespace KDESu;
58 namespace KFI
61 enum EDialogColumns
63 COL_FILE,
64 COL_TRASH,
65 COL_SIZE,
66 COL_DATE,
67 COL_LINK
70 CDuplicatesDialog::CDuplicatesDialog(QWidget *parent, CJobRunner *jr, CFontList *fl)
71 : CActionDialog(parent),
72 itsModifiedSys(false),
73 itsModifiedUser(false),
74 itsRunner(jr),
75 itsFontList(fl)
77 setCaption(i18n("Duplicate Fonts"));
78 setButtons(KDialog::Ok|KDialog::Cancel);
79 enableButtonOk(false);
81 QFrame *page = new QFrame(this);
82 setMainWidget(page);
84 QGridLayout *layout=new QGridLayout(page);
85 layout->setMargin(0);
86 layout->setSpacing(KDialog::spacingHint());
87 itsLabel=new QLabel(page);
88 itsView=new CFontFileListView(page);
89 itsView->hide();
90 layout->addWidget(itsPixmapLabel, 0, 0);
91 layout->addWidget(itsLabel, 0, 1);
92 itsLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
93 layout->addWidget(itsView, 1, 0, 1, 2);
94 itsFontFileList=new CFontFileList(this);
95 connect(itsFontFileList, SIGNAL(finished()), SLOT(scanFinished()));
96 connect(itsView, SIGNAL(haveDeletions(bool)), SLOT(enableButtonOk(bool)));
99 int CDuplicatesDialog::exec()
101 itsModifiedSys=itsModifiedUser=false;
102 itsLabel->setText(i18n("Scanning for duplicate fonts. Please wait..."));
103 itsFontFileList->start();
104 return CActionDialog::exec();
107 void CDuplicatesDialog::scanFinished()
109 stopAnimation();
111 if(itsFontFileList->wasTerminated())
113 itsFontFileList->wait();
114 reject();
116 else
118 CFontFileList::TFontMap duplicates;
120 itsFontFileList->getDuplicateFonts(duplicates);
122 if(0==duplicates.count())
123 itsLabel->setText(i18n("No duplicate fonts found."));
124 else
126 QSize sizeB4(size());
128 itsLabel->setText(i18np("%1 duplicate font found.", "%1 duplicate fonts found.", duplicates.count()));
129 itsView->show();
131 CFontFileList::TFontMap::ConstIterator it(duplicates.begin()),
132 end(duplicates.end());
133 QFont boldFont(font());
135 boldFont.setBold(true);
137 for(; it!=end; ++it)
139 QStringList details;
141 details << FC::createName(it.key().family, it.key().styleInfo);
143 QTreeWidgetItem *top=new QTreeWidgetItem(itsView, details);
145 QStringList::ConstIterator fit((*it).begin()),
146 fend((*it).end());
147 int tt(0),
148 t1(0);
150 for(; fit!=fend; ++fit)
152 QFileInfo info(*fit);
153 details.clear();
154 details.append(*fit);
155 details.append("");
156 details.append(KGlobal::locale()->formatByteSize(info.size()));
157 details.append(KGlobal::locale()->formatDateTime(info.created()));
158 if(info.isSymLink())
159 details.append(info.readLink());
160 new QTreeWidgetItem(top, details);
161 if(Misc::checkExt(*fit, "pfa") || Misc::checkExt(*fit, "pfb"))
162 t1++;
163 else
164 tt++;
166 top->setData(COL_FILE, Qt::DecorationRole,
167 QVariant(SmallIcon(t1>tt ? "application-x-font-type1" : "application-x-font-ttf")));
168 top->setFont(COL_FILE, boldFont);
171 QTreeWidgetItem *item=0L;
172 for(int i=0; item=itsView->topLevelItem(i); ++i)
173 item->setExpanded(true);
175 itsView->setSortingEnabled(true);
176 itsView->header()->resizeSections(QHeaderView::ResizeToContents);
178 int width=(KDialog::marginHint()+itsView->frameWidth()+8)*2;
180 for(int i=0; i<itsView->header()->count(); ++i)
181 width+=itsView->header()->sectionSize(i);
183 width=qMin(QApplication::desktop()->screenGeometry(this).width(), width);
184 resize(width, height());
185 QSize sizeNow(size());
186 if(sizeNow.width()>sizeB4.width())
188 int xmod=(sizeNow.width()-sizeB4.width())/2,
189 ymod=(sizeNow.height()-sizeB4.height())/2;
191 move(pos().x()-xmod, pos().y()-ymod);
197 enum EStatus
199 STATUS_NO_FILES,
200 STATUS_ALL_REMOVED,
201 STATUS_ERROR,
202 STATUS_USER_CANCELLED
205 void CDuplicatesDialog::slotButtonClicked(int button)
207 switch(button)
209 case KDialog::Ok:
211 switch(deleteFiles())
213 case STATUS_NO_FILES:
214 case STATUS_ALL_REMOVED:
215 accept();
216 break;
217 case STATUS_ERROR:
219 QList<QString> files=itsView->getMarkedFiles().toList();
221 if(1==files.count())
222 KMessageBox::error(this, i18n("Could not delete:\n%1", files.first()));
223 else
224 KMessageBox::errorList(this, i18n("Could not delete the following files:"), files);
225 break;
227 default:
228 case STATUS_USER_CANCELLED:
229 break;
231 break;
233 case KDialog::Cancel:
234 if(!itsFontFileList->wasTerminated())
236 if(itsFontFileList->isRunning())
238 if(KMessageBox::Yes==KMessageBox::warningYesNo(this, i18n("Abort font scan?")))
240 itsLabel->setText(i18n("Aborting..."));
242 if(itsFontFileList->isRunning())
243 itsFontFileList->terminate();
244 else
245 reject();
248 else
249 reject();
251 break;
252 default:
253 break;
257 int CDuplicatesDialog::deleteFiles()
259 QSet<QString> files(itsView->getMarkedFiles());
261 if(!files.count())
262 return STATUS_NO_FILES;
264 if(1==files.count()
265 ? KMessageBox::Yes==KMessageBox::warningYesNo(this,
266 i18n("Are you sure you wish to delete:\n%1", files.toList().first()))
267 : KMessageBox::Yes==KMessageBox::warningYesNoList(this,
268 i18n("Are you sure you wish to delete:"), files.toList()))
270 QSet<QString> removed;
272 if(!Misc::root())
274 QSet<QString> sys,
275 user;
276 QSet<QString>::ConstIterator it(files.begin()),
277 end(files.end());
278 QString home(Misc::dirSyntax(QDir::homePath()));
280 for(; it!=end; ++it)
281 if(Misc::root() || 0==(*it).indexOf(home))
282 user.insert(*it);
283 else
284 sys.insert(*it);
286 if(user.count())
287 removed=deleteFiles(user);
288 if(sys.count() && itsRunner->getAdminPasswd(this))
290 static const int constSysDelFiles=16; // Number of files to rm -f in one go...
292 QSet<QString>::ConstIterator it(files.begin()),
293 end(files.end());
294 QStringList delFiles;
296 for(; it!=end; ++it)
298 delFiles.append(*it);
300 if(constSysDelFiles==delFiles.size())
302 removed+=deleteSysFiles(delFiles);
303 delFiles.clear();
307 if(delFiles.count())
308 removed+=deleteSysFiles(delFiles);
311 else
312 removed=deleteFiles(files);
314 itsView->removeFiles(removed);
315 return 0==itsView->getMarkedFiles().count() ? STATUS_ALL_REMOVED : STATUS_ERROR;
317 return STATUS_USER_CANCELLED;
320 QSet<QString> CDuplicatesDialog::deleteFiles(const QSet<QString> &files)
322 QSet<QString> removed;
323 QSet<QString>::ConstIterator it(files.begin()),
324 end(files.end());
326 for(; it!=end; ++it)
327 if(0==::unlink(QFile::encodeName(*it).data()) || !Misc::fExists(*it))
328 removed.insert(*it);
330 if(removed.count())
331 itsModifiedUser=true;
333 return removed;
336 QSet<QString> CDuplicatesDialog::deleteSysFiles(const QStringList &files)
338 QSet<QString> removed;
340 if(files.count())
342 #if defined USE_POLICYKIT && USE_POLICYKIT==1
343 QStringList::ConstIterator it(files.begin()),
344 end(files.end());
345 QDBusInterface iface(KFI_IFACE,
346 "/FontInst",
347 KFI_IFACE,
348 QDBusConnection::systemBus(), this);
350 for(; it!=end; ++it)
352 iface.call("deleteFont", (unsigned int)getpid(), *it);
354 if(!Misc::fExists(*it))
355 removed.insert(*it);
357 #else
358 QByteArray cmd("rm -f");
359 QStringList::ConstIterator it(files.begin()),
360 end(files.end());
362 for(; it!=end; ++it)
364 cmd+=' ';
365 cmd+=QFile::encodeName(KShell::quoteArg(*it));
368 SuProcess proc(KFI_SYS_USER);
370 proc.setCommand(cmd);
371 proc.exec(itsRunner->adminPasswd().toLocal8Bit());
373 for(it=files.begin(); it!=end; ++it)
374 if(!Misc::fExists(*it))
375 removed.insert(*it);
376 #endif
379 if(removed.count())
380 itsModifiedSys=true;
381 return removed;
384 static uint qHash(const CFontFileList::TFile &key)
386 return qHash(key.name.toLower());
389 CFontFileList::CFontFileList(CDuplicatesDialog *parent)
390 : QThread(parent),
391 itsTerminated(false)
395 void CFontFileList::start()
397 if(!isRunning())
399 itsTerminated=false;
400 QThread::start();
404 void CFontFileList::terminate()
406 itsTerminated=true;
409 void CFontFileList::getDuplicateFonts(TFontMap &map)
411 map=itsMap;
413 if(map.count())
415 TFontMap::Iterator it(map.begin()),
416 end(map.end());
418 // Now re-iterate, and remove any entries that only have 1 file...
419 for(it=map.begin(); it!=end; )
420 if((*it).count()<2)
421 it=map.erase(it);
422 else
423 ++it;
427 void CFontFileList::run()
429 const QList<CFamilyItem *> &families(((CDuplicatesDialog *)parent())->fontList()->families());
430 QList<CFamilyItem *>::ConstIterator it(families.begin()),
431 end(families.end());
433 for(; it!=end; ++it)
435 QList<CFontItem *>::ConstIterator fontIt((*it)->fonts().begin()),
436 fontEnd((*it)->fonts().end());
438 for(; fontIt!=fontEnd; ++fontIt)
439 if(!(*fontIt)->isBitmap())
441 Misc::TFont font((*fontIt)->family(), (*fontIt)->styleInfo());
442 CDisabledFonts::TFileList::ConstIterator fileIt((*fontIt)->files().begin()),
443 fileEnd((*fontIt)->files().end());
445 for(; fileIt!=fileEnd; ++fileIt)
446 if(!Misc::isMetrics(*fileIt))
447 itsMap[font].append(*fileIt);
451 // if we have 2 fonts: /wibble/a.ttf and /wibble/a.TTF fontconfig only returns the 1st, so we
452 // now iterate over fontconfig's list, and look for other matching fonts...
453 if(itsMap.count() && !itsTerminated)
455 // Create a map of folder -> set<files>
456 TFontMap::Iterator it(itsMap.begin()),
457 end(itsMap.end());
458 QHash<QString, QSet<TFile> > folderMap;
460 for(int n=0; it!=end && !itsTerminated; ++it)
462 QStringList add;
463 QStringList::const_iterator fIt((*it).begin()),
464 fEnd((*it).end());
466 for(; fIt!=fEnd && !itsTerminated; ++fIt, ++n)
467 folderMap[Misc::getDir(*fIt)].insert(TFile(Misc::getFile(*fIt), it));
470 // Go through our folder map, and check for file duplicates...
471 QHash<QString, QSet<TFile> >::Iterator folderIt(folderMap.begin()),
472 folderEnd(folderMap.end());
474 for(; folderIt!=folderEnd && !itsTerminated; ++folderIt)
475 fileDuplicates(folderIt.key(), *folderIt);
478 emit finished();
481 void CFontFileList::fileDuplicates(const QString &folder, const QSet<TFile> &files)
483 QDir dir(folder);
484 QStringList nameFilters;
485 QSet<TFile>::ConstIterator it(files.begin()),
486 end(files.end());
488 // Filter the QDir to look for filenames matching (caselessly) to those in
489 // files...
490 for(; it!=end; ++it)
491 nameFilters.append((*it).name);
493 dir.setFilter(QDir::Files|QDir::Hidden);
494 dir.setNameFilters(nameFilters);
496 QFileInfoList list(dir.entryInfoList());
498 for (int i = 0; i < list.size() && !itsTerminated; ++i)
500 QFileInfo fileInfo(list.at(i));
502 // Check if this file is already know about - this will do a case-sensitive comparison
503 if(!files.contains(TFile(fileInfo.fileName())))
505 // OK, not found - this means its a duplicate, but different case. So, find the
506 // FontMap iterator, and update its list of files.
507 QSet<TFile>::ConstIterator entry=files.find(TFile(fileInfo.fileName(), true));
509 if(entry!=files.end())
510 (*((*entry).it)).append(fileInfo.absoluteFilePath());
515 inline void markItem(QTreeWidgetItem *item)
517 item->setData(COL_TRASH, Qt::DecorationRole, QVariant(SmallIcon("list-remove")));
520 inline void unmarkItem(QTreeWidgetItem *item)
522 item->setData(COL_TRASH, Qt::DecorationRole, QVariant());
525 inline bool isMarked(QTreeWidgetItem *item)
527 return item->data(COL_TRASH, Qt::DecorationRole).isValid();
530 CFontFileListView::CFontFileListView(QWidget *parent)
531 : QTreeWidget(parent)
533 QStringList headers;
534 headers.append(i18n("Font/File"));
535 headers.append("");
536 headers.append(i18n("Size"));
537 headers.append(i18n("Date"));
538 headers.append(i18n("Links To"));
539 setHeaderLabels(headers);
540 headerItem()->setData(COL_TRASH, Qt::DecorationRole, QVariant(SmallIcon("user-trash")));
541 setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
542 setSelectionMode(ExtendedSelection);
543 sortByColumn(COL_FILE, Qt::AscendingOrder);
544 setSelectionBehavior(SelectRows);
545 setSortingEnabled(true);
546 setAllColumnsShowFocus(true);
547 setAlternatingRowColors(true);
549 itsMenu=new QMenu(this);
550 itsMenu->addAction(KIcon("kfontview"), i18n("Open in Font Viewer"),
551 this, SLOT(openViewer()));
552 itsMenu->addAction(KIcon("document-properties"), i18n("Properties"),
553 this, SLOT(properties()));
554 itsMenu->addSeparator();
555 itsUnMarkAct=itsMenu->addAction(i18n("Unmark for Deletion"),
556 this, SLOT(unmark()));
557 itsMarkAct=itsMenu->addAction(KIcon("edit-delete"), i18n("Mark for Deletion"),
558 this, SLOT(mark()));
560 connect(this, SIGNAL(itemSelectionChanged()), SLOT(selectionChanged()));
561 connect(this, SIGNAL(itemClicked(QTreeWidgetItem *, int)), SLOT(clicked(QTreeWidgetItem *, int)));
564 QSet<QString> CFontFileListView::getMarkedFiles()
566 QTreeWidgetItem *root=invisibleRootItem();
567 QSet<QString> files;
569 for(int t=0; t<root->childCount(); ++t)
571 QList<QTreeWidgetItem *> removeFiles;
572 QTreeWidgetItem *font=root->child(t);
574 for(int c=0; c<font->childCount(); ++c)
576 QTreeWidgetItem *file=font->child(c);
578 if(isMarked(file))
579 files.insert(file->text(0));
583 return files;
586 void CFontFileListView::removeFiles(const QSet<QString> &files)
588 QTreeWidgetItem *root=invisibleRootItem();
589 QList<QTreeWidgetItem *> removeFonts;
591 for(int t=0; t<root->childCount(); ++t)
593 QList<QTreeWidgetItem *> removeFiles;
594 QTreeWidgetItem *font=root->child(t);
596 for(int c=0; c<font->childCount(); ++c)
598 QTreeWidgetItem *file=font->child(c);
600 if(files.contains(file->text(0)))
601 removeFiles.append(file);
604 QList<QTreeWidgetItem *>::ConstIterator it(removeFiles.begin()),
605 end(removeFiles.end());
607 for(; it!=end; ++it)
608 delete (*it);
609 if(0==font->childCount())
610 removeFonts.append(font);
613 QList<QTreeWidgetItem *>::ConstIterator it(removeFonts.begin()),
614 end(removeFonts.end());
615 for(; it!=end; ++it)
616 delete (*it);
619 void CFontFileListView::openViewer()
621 // Number of fonts user has selected, before we ask if they really want to view them all...
622 static const int constMaxBeforePrompt=10;
624 QList<QTreeWidgetItem *> items(selectedItems());
625 QTreeWidgetItem *item;
626 QSet<QString> files;
628 foreach(item, items)
629 if(item->parent()) // Then its a file, not font name :-)
630 files.insert(item->text(0));
632 if(files.count() &&
633 (files.count()<constMaxBeforePrompt ||
634 KMessageBox::Yes==KMessageBox::questionYesNo(this, i18n("Open all %1 fonts in font viewer?", files.count()))))
636 QSet<QString>::ConstIterator it(files.begin()),
637 end(files.end());
639 for(; it!=end; ++it)
641 QStringList args;
643 args << (*it);
645 QProcess::startDetached(KFI_VIEWER, args);
650 void CFontFileListView::properties()
652 QList<QTreeWidgetItem *> items(selectedItems());
653 QTreeWidgetItem *item;
654 KFileItemList files;
656 foreach(item, items)
657 if(item->parent())
658 files.append(KFileItem(KUrl::fromPath(item->text(0)),
659 KMimeType::findByPath(item->text(0))->name(),
660 item->text(COL_LINK).isEmpty() ? S_IFREG : S_IFLNK));
662 if(files.count())
664 KPropertiesDialog dlg(files, this);
665 dlg.exec();
669 void CFontFileListView::mark()
671 QList<QTreeWidgetItem *> items(selectedItems());
672 QTreeWidgetItem *item;
674 foreach(item, items)
675 if(item->parent())
676 markItem(item);
677 checkFiles();
680 void CFontFileListView::unmark()
682 QList<QTreeWidgetItem *> items(selectedItems());
683 QTreeWidgetItem *item;
685 foreach(item, items)
686 if(item->parent())
687 unmarkItem(item);
688 checkFiles();
691 void CFontFileListView::selectionChanged()
693 QList<QTreeWidgetItem *> items(selectedItems());
694 QTreeWidgetItem *item;
696 foreach(item, items)
697 if(!item->parent() && item->isSelected())
698 item->setSelected(false);
701 void CFontFileListView::clicked(QTreeWidgetItem *item, int col)
703 if(item && COL_TRASH==col && item->parent())
705 if(isMarked(item))
706 unmarkItem(item);
707 else
708 markItem(item);
709 checkFiles();
713 void CFontFileListView::contextMenuEvent(QContextMenuEvent *ev)
715 QTreeWidgetItem *item(itemAt(ev->pos()));
717 if(item && item->parent())
719 if(!item->isSelected())
720 item->setSelected(true);
722 bool haveUnmarked(false),
723 haveMaked(false);
725 QList<QTreeWidgetItem *> items(selectedItems());
726 QTreeWidgetItem *item;
728 foreach(item, items)
730 if(item->parent() && item->isSelected())
731 if(isMarked(item))
732 haveMaked=true;
733 else
734 haveUnmarked=true;
736 if(haveUnmarked && haveMaked)
737 break;
740 itsMarkAct->setEnabled(haveUnmarked);
741 itsUnMarkAct->setEnabled(haveMaked);
742 itsMenu->popup(ev->globalPos());
746 void CFontFileListView::checkFiles()
748 // Need to check that if we mark a file that is linked to, then we also need
749 // to mark the sym link.
750 QSet<QString> marked(getMarkedFiles());
752 if(marked.count())
754 QTreeWidgetItem *root=invisibleRootItem();
756 for(int t=0; t<root->childCount(); ++t)
758 QTreeWidgetItem *font=root->child(t);
760 for(int c=0; c<font->childCount(); ++c)
762 QTreeWidgetItem *file=font->child(c);
763 QString link(font->child(c)->text(COL_LINK));
765 if(!link.isEmpty() && marked.contains(link))
766 if(!isMarked(file))
767 markItem(file);
771 emit haveDeletions(true);
773 else
774 emit haveDeletions(false);
779 #include "DuplicatesDialog.moc"