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 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"
28 #include "JobRunner.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>
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>
56 using namespace KDESu
;
70 CDuplicatesDialog::CDuplicatesDialog(QWidget
*parent
, CJobRunner
*jr
, CFontList
*fl
)
71 : CActionDialog(parent
),
72 itsModifiedSys(false),
73 itsModifiedUser(false),
77 setCaption(i18n("Duplicate Fonts"));
78 setButtons(KDialog::Ok
|KDialog::Cancel
);
79 enableButtonOk(false);
81 QFrame
*page
= new QFrame(this);
84 QGridLayout
*layout
=new QGridLayout(page
);
86 layout
->setSpacing(KDialog::spacingHint());
87 itsLabel
=new QLabel(page
);
88 itsView
=new CFontFileListView(page
);
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()
111 if(itsFontFileList
->wasTerminated())
113 itsFontFileList
->wait();
118 CFontFileList::TFontMap duplicates
;
120 itsFontFileList
->getDuplicateFonts(duplicates
);
122 if(0==duplicates
.count())
123 itsLabel
->setText(i18n("No duplicate fonts found."));
126 QSize
sizeB4(size());
128 itsLabel
->setText(i18np("%1 duplicate font found.", "%1 duplicate fonts found.", duplicates
.count()));
131 CFontFileList::TFontMap::ConstIterator
it(duplicates
.begin()),
132 end(duplicates
.end());
133 QFont
boldFont(font());
135 boldFont
.setBold(true);
141 details
<< FC::createName(it
.key().family
, it
.key().styleInfo
);
143 QTreeWidgetItem
*top
=new QTreeWidgetItem(itsView
, details
);
145 QStringList::ConstIterator
fit((*it
).begin()),
150 for(; fit
!=fend
; ++fit
)
152 QFileInfo
info(*fit
);
154 details
.append(*fit
);
156 details
.append(KGlobal::locale()->formatByteSize(info
.size()));
157 details
.append(KGlobal::locale()->formatDateTime(info
.created()));
159 details
.append(info
.readLink());
160 new QTreeWidgetItem(top
, details
);
161 if(Misc::checkExt(*fit
, "pfa") || Misc::checkExt(*fit
, "pfb"))
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
);
202 STATUS_USER_CANCELLED
205 void CDuplicatesDialog::slotButtonClicked(int button
)
211 switch(deleteFiles())
213 case STATUS_NO_FILES
:
214 case STATUS_ALL_REMOVED
:
219 QList
<QString
> files
=itsView
->getMarkedFiles().toList();
222 KMessageBox::error(this, i18n("Could not delete:\n%1", files
.first()));
224 KMessageBox::errorList(this, i18n("Could not delete the following files:"), files
);
228 case STATUS_USER_CANCELLED
:
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();
257 int CDuplicatesDialog::deleteFiles()
259 QSet
<QString
> files(itsView
->getMarkedFiles());
262 return STATUS_NO_FILES
;
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
;
276 QSet
<QString
>::ConstIterator
it(files
.begin()),
278 QString
home(Misc::dirSyntax(QDir::homePath()));
281 if(Misc::root() || 0==(*it
).indexOf(home
))
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()),
294 QStringList delFiles
;
298 delFiles
.append(*it
);
300 if(constSysDelFiles
==delFiles
.size())
302 removed
+=deleteSysFiles(delFiles
);
308 removed
+=deleteSysFiles(delFiles
);
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()),
327 if(0==::unlink(QFile::encodeName(*it
).data()) || !Misc::fExists(*it
))
331 itsModifiedUser
=true;
336 QSet
<QString
> CDuplicatesDialog::deleteSysFiles(const QStringList
&files
)
338 QSet
<QString
> removed
;
342 #if defined USE_POLICYKIT && USE_POLICYKIT==1
343 QStringList::ConstIterator
it(files
.begin()),
345 QDBusInterface
iface(KFI_IFACE
,
348 QDBusConnection::systemBus(), this);
352 iface
.call("deleteFont", (unsigned int)getpid(), *it
);
354 if(!Misc::fExists(*it
))
358 QByteArray
cmd("rm -f");
359 QStringList::ConstIterator
it(files
.begin()),
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
))
384 static uint
qHash(const CFontFileList::TFile
&key
)
386 return qHash(key
.name
.toLower());
389 CFontFileList::CFontFileList(CDuplicatesDialog
*parent
)
395 void CFontFileList::start()
404 void CFontFileList::terminate()
409 void CFontFileList::getDuplicateFonts(TFontMap
&map
)
415 TFontMap::Iterator
it(map
.begin()),
418 // Now re-iterate, and remove any entries that only have 1 file...
419 for(it
=map
.begin(); it
!=end
; )
427 void CFontFileList::run()
429 const QList
<CFamilyItem
*> &families(((CDuplicatesDialog
*)parent())->fontList()->families());
430 QList
<CFamilyItem
*>::ConstIterator
it(families
.begin()),
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()),
458 QHash
<QString
, QSet
<TFile
> > folderMap
;
460 for(int n
=0; it
!=end
&& !itsTerminated
; ++it
)
463 QStringList::const_iterator
fIt((*it
).begin()),
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
);
481 void CFontFileList::fileDuplicates(const QString
&folder
, const QSet
<TFile
> &files
)
484 QStringList nameFilters
;
485 QSet
<TFile
>::ConstIterator
it(files
.begin()),
488 // Filter the QDir to look for filenames matching (caselessly) to those in
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
)
534 headers
.append(i18n("Font/File"));
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"),
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();
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
);
579 files
.insert(file
->text(0));
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());
609 if(0==font
->childCount())
610 removeFonts
.append(font
);
613 QList
<QTreeWidgetItem
*>::ConstIterator
it(removeFonts
.begin()),
614 end(removeFonts
.end());
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
;
629 if(item
->parent()) // Then its a file, not font name :-)
630 files
.insert(item
->text(0));
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()),
645 QProcess::startDetached(KFI_VIEWER
, args
);
650 void CFontFileListView::properties()
652 QList
<QTreeWidgetItem
*> items(selectedItems());
653 QTreeWidgetItem
*item
;
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
));
664 KPropertiesDialog
dlg(files
, this);
669 void CFontFileListView::mark()
671 QList
<QTreeWidgetItem
*> items(selectedItems());
672 QTreeWidgetItem
*item
;
680 void CFontFileListView::unmark()
682 QList
<QTreeWidgetItem
*> items(selectedItems());
683 QTreeWidgetItem
*item
;
691 void CFontFileListView::selectionChanged()
693 QList
<QTreeWidgetItem
*> items(selectedItems());
694 QTreeWidgetItem
*item
;
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())
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),
725 QList
<QTreeWidgetItem
*> items(selectedItems());
726 QTreeWidgetItem
*item
;
730 if(item
->parent() && item
->isSelected())
736 if(haveUnmarked
&& haveMaked
)
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());
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
))
771 emit
haveDeletions(true);
774 emit
haveDeletions(false);
779 #include "DuplicatesDialog.moc"