2
* KFontInst - KDE Font Installer
4
* Copyright 2003-2007 Craig Drummond <craig@kde.org>
8
* This program is free software; you can redistribute it and/or
9
* modify it under the terms of the GNU General Public
10
* License version 2 as published by the Free Software Foundation.
12
* This program is distributed in the hope that it will be useful,
13
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
* General Public License for more details.
17
* You should have received a copy of the GNU General Public License
18
* along with this program; see the file COPYING. If not, write to
19
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20
* Boston, MA 02110-1301, USA.
23
#include "DuplicatesDialog.h"
26
#include "JobRunner.h"
29
#include <kiconloader.h>
31
#include <kmessagebox.h>
33
#include <kfileitem.h>
34
#include <kpropertiesdialog.h>
38
#include <QGridLayout>
40
#include <QFileInfoList>
42
#include <QHeaderView>
45
#include <QContextMenuEvent>
47
#include <QApplication>
48
#include <QDesktopWidget>
63
CDuplicatesDialog::CDuplicatesDialog(QWidget *parent, CJobRunner *jr, CFontList *fl)
64
: CActionDialog(parent),
65
itsModifiedSys(false),
66
itsModifiedUser(false),
70
setCaption(i18n("Duplicate Fonts"));
71
setButtons(KDialog::Ok|KDialog::Cancel);
72
enableButtonOk(false);
74
QFrame *page = new QFrame(this);
77
QGridLayout *layout=new QGridLayout(page);
79
layout->setSpacing(KDialog::spacingHint());
80
itsLabel=new QLabel(page);
81
itsView=new CFontFileListView(page);
83
layout->addWidget(itsPixmapLabel, 0, 0);
84
layout->addWidget(itsLabel, 0, 1);
85
itsLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
86
layout->addWidget(itsView, 1, 0, 1, 2);
87
itsFontFileList=new CFontFileList(this);
88
connect(itsFontFileList, SIGNAL(finished()), SLOT(scanFinished()));
89
connect(itsView, SIGNAL(haveDeletions(bool)), SLOT(enableButtonOk(bool)));
92
int CDuplicatesDialog::exec()
94
itsModifiedSys=itsModifiedUser=false;
95
itsLabel->setText(i18n("Scanning for duplicate fonts. Please wait..."));
96
itsFontFileList->start();
97
return CActionDialog::exec();
100
void CDuplicatesDialog::scanFinished()
104
if(itsFontFileList->wasTerminated())
106
itsFontFileList->wait();
111
CFontFileList::TFontMap duplicates;
113
itsFontFileList->getDuplicateFonts(duplicates);
115
if(0==duplicates.count())
116
itsLabel->setText(i18n("No duplicate fonts found."));
119
QSize sizeB4(size());
121
itsLabel->setText(i18n("%1 duplicate fonts found.", duplicates.count()));
124
CFontFileList::TFontMap::ConstIterator it(duplicates.begin()),
125
end(duplicates.end());
126
QFont boldFont(font());
128
boldFont.setBold(true);
134
details << FC::createName(it.key().family, it.key().styleInfo);
136
QTreeWidgetItem *top=new QTreeWidgetItem(itsView, details);
138
QStringList::ConstIterator fit((*it).begin()),
143
for(; fit!=fend; ++fit)
145
QFileInfo info(*fit);
147
details.append(*fit);
149
details.append(KGlobal::locale()->formatByteSize(info.size()));
150
details.append(KGlobal::locale()->formatDateTime(info.created()));
152
details.append(info.readLink());
153
new QTreeWidgetItem(top, details);
154
if(Misc::checkExt(*fit, "pfa") || Misc::checkExt(*fit, "pfb"))
159
top->setExpanded(true);
160
top->setData(COL_FILE, Qt::DecorationRole,
161
QVariant(SmallIcon(t1>tt ? "font-type1" : "font-truetype")));
162
top->setFont(COL_FILE, boldFont);
164
itsView->setSortingEnabled(true);
165
itsView->header()->resizeSections(QHeaderView::ResizeToContents);
167
int width=(KDialog::marginHint()+itsView->frameWidth()+8)*2;
169
for(int i=0; i<itsView->header()->count(); ++i)
170
width+=itsView->header()->sectionSize(i);
172
width=qMin(QApplication::desktop()->screenGeometry(this).width(), width);
173
resize(width, height());
174
QSize sizeNow(size());
175
if(sizeNow.width()>sizeB4.width())
177
int xmod=(sizeNow.width()-sizeB4.width())/2,
178
ymod=(sizeNow.height()-sizeB4.height())/2;
180
move(pos().x()-xmod, pos().y()-ymod);
191
STATUS_USER_CANCELLED
194
void CDuplicatesDialog::slotButtonClicked(int button)
200
switch(deleteFiles())
202
case STATUS_NO_FILES:
203
case STATUS_ALL_REMOVED:
208
QList<QString> files=itsView->getMarkedFiles().toList();
211
KMessageBox::error(this, i18n("Could not delete:\n%1", files.first()));
213
KMessageBox::errorList(this, i18n("Could not delete the following files:"), files);
217
case STATUS_USER_CANCELLED:
222
case KDialog::Cancel:
223
if(!itsFontFileList->wasTerminated())
225
if(itsFontFileList->isRunning())
227
if(KMessageBox::Yes==KMessageBox::warningYesNo(this, i18n("Abort font scan?")))
229
itsLabel->setText("Aborting...");
231
if(itsFontFileList->isRunning())
232
itsFontFileList->terminate();
246
int CDuplicatesDialog::deleteFiles()
248
QSet<QString> files(itsView->getMarkedFiles());
251
return STATUS_NO_FILES;
254
? KMessageBox::Yes==KMessageBox::warningYesNo(this,
255
i18n("Are you sure you wish to delete:\n%1", files.toList().first()))
256
: KMessageBox::Yes==KMessageBox::warningYesNoList(this,
257
i18n("Are you sure you wish to delete:"), files.toList()))
259
QSet<QString> removed;
265
QSet<QString>::ConstIterator it(files.begin()),
267
QString home(Misc::dirSyntax(QDir::homePath()));
270
if(Misc::root() || 0==(*it).indexOf(home))
276
removed=deleteFiles(user);
277
if(sys.count() && itsRunner->getAdminPasswd(this))
279
static const int constSysDelFiles=16; // Number of files to rm -f in one go...
281
QSet<QString>::ConstIterator it(files.begin()),
283
QStringList delFiles;
287
delFiles.append(*it);
289
if(constSysDelFiles==delFiles.size())
291
removed+=deleteSysFiles(delFiles);
297
removed+=deleteSysFiles(delFiles);
301
removed=deleteFiles(files);
303
itsView->removeFiles(removed);
304
return 0==itsView->getMarkedFiles().count() ? STATUS_ALL_REMOVED : STATUS_ERROR;
306
return STATUS_USER_CANCELLED;
309
QSet<QString> CDuplicatesDialog::deleteFiles(const QSet<QString> &files)
311
QSet<QString> removed;
312
QSet<QString>::ConstIterator it(files.begin()),
316
if(0==::unlink(QFile::encodeName(*it).data()) || !Misc::fExists(*it))
320
itsModifiedUser=true;
325
QSet<QString> CDuplicatesDialog::deleteSysFiles(const QStringList &files)
327
QSet<QString> removed;
331
QByteArray cmd("rm -f");
332
QStringList::ConstIterator it(files.begin()),
338
cmd+=QFile::encodeName(KShell::quoteArg(*it));
341
SuProcess proc(KFI_SYS_USER);
343
proc.setCommand(cmd);
344
proc.exec(itsRunner->adminPasswd().toLocal8Bit());
346
for(it=files.begin(); it!=end; ++it)
347
if(!Misc::fExists(*it))
356
static uint qHash(const CFontFileList::TFile &key)
358
return qHash(key.name.toLower());
361
CFontFileList::CFontFileList(CDuplicatesDialog *parent)
367
void CFontFileList::start()
376
void CFontFileList::terminate()
381
void CFontFileList::getDuplicateFonts(TFontMap &map)
387
TFontMap::Iterator it(map.begin()),
390
// Now re-iterate, and remove any entries that only have 1 file...
391
for(it=map.begin(); it!=end; )
399
void CFontFileList::run()
401
const QList<CFamilyItem *> &families(((CDuplicatesDialog *)parent())->fontList()->families());
402
QList<CFamilyItem *>::ConstIterator it(families.begin()),
407
QList<CFontItem *>::ConstIterator fontIt((*it)->fonts().begin()),
408
fontEnd((*it)->fonts().end());
410
for(; fontIt!=fontEnd; ++fontIt)
411
if(!(*fontIt)->isBitmap())
413
Misc::TFont font((*fontIt)->family(), (*fontIt)->styleInfo());
414
CDisabledFonts::TFileList::ConstIterator fileIt((*fontIt)->files().begin()),
415
fileEnd((*fontIt)->files().end());
417
for(; fileIt!=fileEnd; ++fileIt)
418
if(!Misc::isMetrics(*fileIt))
419
itsMap[font].append(*fileIt);
423
// if we have 2 fonts: /wibble/a.ttf and /wibble/a.TTF fontconfig only returns the 1st, so we
424
// now iterate over fontconfig's list, and look for other matching fonts...
425
if(itsMap.count() && !itsTerminated)
427
// Create a map of folder -> set<files>
428
TFontMap::Iterator it(itsMap.begin()),
430
QHash<QString, QSet<TFile> > folderMap;
432
for(int n=0; it!=end && !itsTerminated; ++it)
435
QStringList::Iterator fIt((*it).begin()),
438
for(; fIt!=fEnd && !itsTerminated; ++fIt, ++n)
439
folderMap[Misc::getDir(*fIt)].insert(TFile(Misc::getFile(*fIt), it));
442
// Go through our folder map, and check for file duplicates...
443
QHash<QString, QSet<TFile> >::Iterator folderIt(folderMap.begin()),
444
folderEnd(folderMap.end());
446
for(; folderIt!=folderEnd && !itsTerminated; ++folderIt)
447
fileDuplicates(folderIt.key(), *folderIt);
453
void CFontFileList::fileDuplicates(const QString &folder, const QSet<TFile> &files)
456
QStringList nameFilters;
457
QSet<TFile>::ConstIterator it(files.begin()),
460
// Filter the QDir to look for filenames matching (caselessly) to those in
463
nameFilters.append((*it).name);
465
dir.setFilter(QDir::Files|QDir::Hidden);
466
dir.setNameFilters(nameFilters);
468
QFileInfoList list(dir.entryInfoList());
470
for (int i = 0; i < list.size() && !itsTerminated; ++i)
472
QFileInfo fileInfo(list.at(i));
474
// Check if this file is already know about - this will do a case-sensitive comparison
475
if(!files.contains(TFile(fileInfo.fileName())))
477
// OK, not found - this means its a duplicate, but different case. So, find the
478
// FontMap iterator, and update its list of files.
479
QSet<TFile>::ConstIterator entry=files.find(TFile(fileInfo.fileName(), true));
481
if(entry!=files.end())
482
(*((*entry).it)).append(fileInfo.absoluteFilePath());
487
inline void markItem(QTreeWidgetItem *item)
489
item->setData(COL_TRASH, Qt::DecorationRole, QVariant(SmallIcon("list-remove")));
492
inline void unmarkItem(QTreeWidgetItem *item)
494
item->setData(COL_TRASH, Qt::DecorationRole, QVariant());
497
inline bool isMarked(QTreeWidgetItem *item)
499
return item->data(COL_TRASH, Qt::DecorationRole).isValid();
502
CFontFileListView::CFontFileListView(QWidget *parent)
503
: QTreeWidget(parent)
506
headers.append(i18n("Font/File"));
508
headers.append(i18n("Size"));
509
headers.append(i18n("Date"));
510
headers.append(i18n("Links To"));
511
setHeaderLabels(headers);
512
headerItem()->setData(COL_TRASH, Qt::DecorationRole, QVariant(SmallIcon("edit-trash")));
513
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
514
setSelectionMode(ExtendedSelection);
515
sortByColumn(COL_FILE, Qt::AscendingOrder);
516
setSelectionBehavior(SelectRows);
517
setSortingEnabled(true);
518
setAllColumnsShowFocus(true);
519
setAlternatingRowColors(true);
521
itsMenu=new QMenu(this);
522
itsMenu->addAction(KIcon("kfontview"), i18n("Open in Font Viewer..."),
523
this, SLOT(openViewer()));
524
itsMenu->addAction(KIcon("document-properties"), i18n("Properties..."),
525
this, SLOT(properties()));
526
itsMenu->addSeparator();
527
itsUnMarkAct=itsMenu->addAction(i18n("Unmark for deletion..."),
528
this, SLOT(unmark()));
529
itsMarkAct=itsMenu->addAction(KIcon("edit-delete"), i18n("Mark for deletion..."),
532
connect(this, SIGNAL(itemSelectionChanged()), SLOT(selectionChanged()));
533
connect(this, SIGNAL(itemClicked(QTreeWidgetItem *, int)), SLOT(clicked(QTreeWidgetItem *, int)));
536
QSet<QString> CFontFileListView::getMarkedFiles()
538
QTreeWidgetItem *root=invisibleRootItem();
541
for(int t=0; t<root->childCount(); ++t)
543
QList<QTreeWidgetItem *> removeFiles;
544
QTreeWidgetItem *font=root->child(t);
546
for(int c=0; c<font->childCount(); ++c)
548
QTreeWidgetItem *file=font->child(c);
551
files.insert(file->text(0));
558
void CFontFileListView::removeFiles(const QSet<QString> &files)
560
QTreeWidgetItem *root=invisibleRootItem();
561
QList<QTreeWidgetItem *> removeFonts;
563
for(int t=0; t<root->childCount(); ++t)
565
QList<QTreeWidgetItem *> removeFiles;
566
QTreeWidgetItem *font=root->child(t);
568
for(int c=0; c<font->childCount(); ++c)
570
QTreeWidgetItem *file=font->child(c);
572
if(files.contains(file->text(0)))
573
removeFiles.append(file);
576
QList<QTreeWidgetItem *>::ConstIterator it(removeFiles.begin()),
577
end(removeFiles.end());
581
if(0==font->childCount())
582
removeFonts.append(font);
585
QList<QTreeWidgetItem *>::ConstIterator it(removeFonts.begin()),
586
end(removeFonts.end());
591
void CFontFileListView::openViewer()
593
// Number of fonts user has selected, before we ask if they really want to view them all...
594
static const int constMaxBeforePrompt=10;
596
QList<QTreeWidgetItem *> items(selectedItems());
597
QTreeWidgetItem *item;
601
if(item->parent()) // Then its a file, not font name :-)
602
files.insert(item->text(0));
605
(files.count()<constMaxBeforePrompt ||
606
KMessageBox::Yes==KMessageBox::questionYesNo(this, i18n("Open all %1 fonts in font viewer?", files.count()))))
608
QSet<QString>::ConstIterator it(files.begin()),
617
QProcess::startDetached(KFI_VIEWER, args);
622
void CFontFileListView::properties()
624
QList<QTreeWidgetItem *> items(selectedItems());
625
QTreeWidgetItem *item;
630
files.append(new KFileItem(KUrl::fromPath(item->text(0)),
631
KMimeType::findByPath(item->text(0))->name(),
632
item->text(COL_LINK).isEmpty() ? S_IFREG : S_IFLNK));
636
KPropertiesDialog dlg(files, this);
639
KFileItemList::ConstIterator it(files.begin()),
647
void CFontFileListView::mark()
649
QList<QTreeWidgetItem *> items(selectedItems());
650
QTreeWidgetItem *item;
658
void CFontFileListView::unmark()
660
QList<QTreeWidgetItem *> items(selectedItems());
661
QTreeWidgetItem *item;
669
void CFontFileListView::selectionChanged()
671
QList<QTreeWidgetItem *> items(selectedItems());
672
QTreeWidgetItem *item;
675
if(!item->parent() && item->isSelected())
676
item->setSelected(false);
679
void CFontFileListView::clicked(QTreeWidgetItem *item, int col)
681
if(item && COL_TRASH==col && item->parent())
691
void CFontFileListView::contextMenuEvent(QContextMenuEvent *ev)
693
QTreeWidgetItem *item(itemAt(ev->pos()));
695
if(item && item->parent())
697
if(!item->isSelected())
698
item->setSelected(true);
700
bool haveUnmarked(false),
703
QList<QTreeWidgetItem *> items(selectedItems());
704
QTreeWidgetItem *item;
708
if(item->parent() && item->isSelected())
714
if(haveUnmarked && haveMaked)
718
itsMarkAct->setEnabled(haveUnmarked);
719
itsUnMarkAct->setEnabled(haveMaked);
720
itsMenu->popup(ev->globalPos());
724
void CFontFileListView::checkFiles()
726
// Need to check that if we mark a file that is linked to, then we also need
727
// to mark the sym link.
728
QSet<QString> marked(getMarkedFiles());
732
QTreeWidgetItem *root=invisibleRootItem();
734
for(int t=0; t<root->childCount(); ++t)
736
QTreeWidgetItem *font=root->child(t);
738
for(int c=0; c<font->childCount(); ++c)
740
QTreeWidgetItem *file=font->child(c);
741
QString link(font->child(c)->text(COL_LINK));
743
if(!link.isEmpty() && marked.contains(link))
749
emit haveDeletions(true);
752
emit haveDeletions(false);
757
#include "DuplicatesDialog.moc"