• Skip to content
  • Skip to link menu
Trinity API Reference
  • Trinity API Reference
  • kutils
 

kutils

  • kutils
kfinddialog.cpp
1 /*
2  Copyright (C) 2001, S.R.Haque <srhaque@iee.org>.
3  Copyright (C) 2002, David Faure <david@mandrakesoft.com>
4  This file is part of the KDE project
5 
6  This library is free software; you can redistribute it and/or
7  modify it under the terms of the GNU Library General Public
8  License version 2, as published by the Free Software Foundation.
9 
10  This library is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  Library General Public License for more details.
14 
15  You should have received a copy of the GNU Library General Public License
16  along with this library; see the file COPYING.LIB. If not, write to
17  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  Boston, MA 02110-1301, USA.
19 */
20 
21 #include "kfinddialog.h"
22 #include <tqcheckbox.h>
23 #include <tqcursor.h>
24 #include <tqgroupbox.h>
25 #include <tqlabel.h>
26 #include <tqlayout.h>
27 #include <tqpopupmenu.h>
28 #include <tqpushbutton.h>
29 #include <tqregexp.h>
30 #include <kcombobox.h>
31 #include <kdebug.h>
32 #include <klocale.h>
33 #include <kmessagebox.h>
34 #include <assert.h>
35 #include <tqwhatsthis.h>
36 
37 #include <kregexpeditorinterface.h>
38 #include <kparts/componentfactory.h>
39 
40 class KFindDialog::KFindDialogPrivate
41 {
42 public:
43  KFindDialogPrivate() : m_regexpDialog(0),
44  m_regexpDialogQueryDone(false),
45  m_enabled(WholeWordsOnly | FromCursor | SelectedText | CaseSensitive | FindBackwards | RegularExpression), m_initialShowDone(false) {}
46  TQDialog* m_regexpDialog;
47  bool m_regexpDialogQueryDone;
48  long m_enabled; // uses Options to define which search options are enabled
49  bool m_initialShowDone;
50  TQStringList findStrings;
51  TQString pattern;
52 };
53 
54 KFindDialog::KFindDialog(TQWidget *parent, const char *name, long options, const TQStringList &findStrings, bool hasSelection) :
55  KDialogBase(parent, name, true, i18n("Find Text"), Ok | Cancel, Ok),
56  m_findExtension (0),
57  m_replaceExtension (0)
58 {
59  d = new KFindDialogPrivate;
60  init(false, findStrings, hasSelection);
61  setOptions(options);
62  setButtonCancel( KStdGuiItem::close() );
63 }
64 
65 KFindDialog::KFindDialog(bool modal, TQWidget *parent, const char *name, long options, const TQStringList &findStrings, bool hasSelection) :
66  KDialogBase(parent, name, modal, i18n("Find Text"), Ok | Cancel, Ok),
67  m_findExtension (0),
68  m_replaceExtension (0)
69 {
70  d = new KFindDialogPrivate;
71  init(false, findStrings, hasSelection);
72  setOptions(options);
73  setButtonCancel( KStdGuiItem::close() );
74 }
75 
76 KFindDialog::KFindDialog(TQWidget *parent, const char *name, bool /*forReplace*/) :
77  KDialogBase(parent, name, true, i18n("Replace Text"), Ok | Cancel, Ok),
78  m_findExtension (0),
79  m_replaceExtension (0)
80 {
81  d = new KFindDialogPrivate;
82  setButtonCancel( KStdGuiItem::close() );
83 }
84 
85 KFindDialog::~KFindDialog()
86 {
87  delete d;
88 }
89 
90 TQWidget *KFindDialog::findExtension()
91 {
92  if (!m_findExtension)
93  {
94  m_findExtension = new TQWidget(m_findGrp);
95  m_findLayout->addMultiCellWidget(m_findExtension, 3, 3, 0, 1);
96  }
97 
98  return m_findExtension;
99 }
100 
101 TQStringList KFindDialog::findHistory() const
102 {
103  return m_find->historyItems();
104 }
105 
106 void KFindDialog::init(bool forReplace, const TQStringList &findStrings, bool hasSelection)
107 {
108  TQVBoxLayout *topLayout;
109  TQGridLayout *optionsLayout;
110 
111  // Create common parts of dialog.
112  TQWidget *page = new TQWidget(this);
113  setMainWidget(page);
114 
115  topLayout = new TQVBoxLayout(page);
116  topLayout->setSpacing( KDialog::spacingHint() );
117  topLayout->setMargin( 0 );
118 
119  m_findGrp = new TQGroupBox(0, Qt::Vertical, i18n("Find"), page);
120  m_findGrp->layout()->setSpacing( KDialog::spacingHint() );
121  // m_findGrp->layout()->setMargin( KDialog::marginHint() );
122  m_findLayout = new TQGridLayout(m_findGrp->layout());
123  m_findLayout->setSpacing( KDialog::spacingHint() );
124  // m_findLayout->setMargin( KDialog::marginHint() );
125 
126  m_findLabel = new TQLabel(i18n("&Text to find:"), m_findGrp);
127  m_find = new KHistoryCombo(true, m_findGrp);
128  m_find->setMaxCount(10);
129  m_find->setDuplicatesEnabled(false);
130  m_regExp = new TQCheckBox(i18n("Regular e&xpression"), m_findGrp);
131  m_regExpItem = new TQPushButton(i18n("&Edit..."), m_findGrp);
132  m_regExpItem->setEnabled(false);
133 
134  m_findLayout->addWidget(m_findLabel, 0, 0);
135  m_findLayout->addMultiCellWidget(m_find, 1, 1, 0, 1);
136  m_findLayout->addWidget(m_regExp, 2, 0);
137  m_findLayout->addWidget(m_regExpItem, 2, 1);
138  topLayout->addWidget(m_findGrp);
139 
140  m_replaceGrp = new TQGroupBox(0, Qt::Vertical, i18n("Replace With"), page);
141  m_replaceGrp->layout()->setSpacing( KDialog::spacingHint() );
142  // m_replaceGrp->layout()->setMargin( KDialog::marginHint() );
143  m_replaceLayout = new TQGridLayout(m_replaceGrp->layout());
144  m_replaceLayout->setSpacing( KDialog::spacingHint() );
145 // m_replaceLayout->setMargin( KDialog::marginHint() );
146 
147  m_replaceLabel = new TQLabel(i18n("Replace&ment text:"), m_replaceGrp);
148  m_replace = new KHistoryCombo(true, m_replaceGrp);
149  m_replace->setMaxCount(10);
150  m_replace->setDuplicatesEnabled(false);
151  m_backRef = new TQCheckBox(i18n("Use p&laceholders"), m_replaceGrp);
152  m_backRefItem = new TQPushButton(i18n("Insert Place&holder"), m_replaceGrp);
153  m_backRefItem->setEnabled(false);
154 
155  m_replaceLayout->addWidget(m_replaceLabel, 0, 0);
156  m_replaceLayout->addMultiCellWidget(m_replace, 1, 1, 0, 1);
157  m_replaceLayout->addWidget(m_backRef, 2, 0);
158  m_replaceLayout->addWidget(m_backRefItem, 2, 1);
159  topLayout->addWidget(m_replaceGrp);
160 
161  m_optionGrp = new TQGroupBox(0, Qt::Vertical, i18n("Options"), page);
162  m_optionGrp->layout()->setSpacing(KDialog::spacingHint());
163  // m_optionGrp->layout()->setMargin(KDialog::marginHint());
164  optionsLayout = new TQGridLayout(m_optionGrp->layout());
165  optionsLayout->setSpacing( KDialog::spacingHint() );
166  // optionsLayout->setMargin( KDialog::marginHint() );
167 
168  m_caseSensitive = new TQCheckBox(i18n("C&ase sensitive"), m_optionGrp);
169  m_wholeWordsOnly = new TQCheckBox(i18n("&Whole words only"), m_optionGrp);
170  m_fromCursor = new TQCheckBox(i18n("From c&ursor"), m_optionGrp);
171  m_findBackwards = new TQCheckBox(i18n("Find &backwards"), m_optionGrp);
172  m_selectedText = new TQCheckBox(i18n("&Selected text"), m_optionGrp);
173  setHasSelection( hasSelection );
174  // If we have a selection, we make 'find in selection' default
175  // and if we don't, then the option has to be unchecked, obviously.
176  m_selectedText->setChecked( hasSelection );
177  slotSelectedTextToggled( hasSelection );
178 
179  m_promptOnReplace = new TQCheckBox(i18n("&Prompt on replace"), m_optionGrp);
180  m_promptOnReplace->setChecked( true );
181 
182  optionsLayout->addWidget(m_caseSensitive, 0, 0);
183  optionsLayout->addWidget(m_wholeWordsOnly, 1, 0);
184  optionsLayout->addWidget(m_fromCursor, 2, 0);
185  optionsLayout->addWidget(m_findBackwards, 0, 1);
186  optionsLayout->addWidget(m_selectedText, 1, 1);
187  optionsLayout->addWidget(m_promptOnReplace, 2, 1);
188  topLayout->addWidget(m_optionGrp);
189 
190  // We delay creation of these until needed.
191  m_patterns = 0L;
192  m_placeholders = 0L;
193 
194  // signals and slots connections
195  connect(m_selectedText, TQT_SIGNAL(toggled(bool)), this, TQT_SLOT(slotSelectedTextToggled(bool)));
196  connect(m_regExp, TQT_SIGNAL(toggled(bool)), m_regExpItem, TQT_SLOT(setEnabled(bool)));
197  connect(m_backRef, TQT_SIGNAL(toggled(bool)), m_backRefItem, TQT_SLOT(setEnabled(bool)));
198  connect(m_regExpItem, TQT_SIGNAL(clicked()), this, TQT_SLOT(showPatterns()));
199  connect(m_backRefItem, TQT_SIGNAL(clicked()), this, TQT_SLOT(showPlaceholders()));
200 
201  connect(m_find, TQT_SIGNAL(textChanged ( const TQString & )),this, TQT_SLOT(textSearchChanged( const TQString & )));
202 
203  // tab order
204  setTabOrder(m_find, m_regExp);
205  setTabOrder(m_regExp, m_regExpItem);
206  setTabOrder(m_regExpItem, m_replace);
207  setTabOrder(m_replace, m_backRef);
208  setTabOrder(m_backRef, m_backRefItem);
209  setTabOrder(m_backRefItem, m_caseSensitive);
210  setTabOrder(m_caseSensitive, m_wholeWordsOnly);
211  setTabOrder(m_wholeWordsOnly, m_fromCursor);
212  setTabOrder(m_fromCursor, m_findBackwards);
213  setTabOrder(m_findBackwards, m_selectedText);
214  setTabOrder(m_selectedText, m_promptOnReplace);
215 
216  // buddies
217  m_findLabel->setBuddy(m_find);
218  m_replaceLabel->setBuddy(m_replace);
219 
220  if (!forReplace)
221  {
222  m_promptOnReplace->hide();
223  m_replaceGrp->hide();
224  }
225 
226  d->findStrings = findStrings;
227  m_find->setFocus();
228  enableButtonOK( !pattern().isEmpty() );
229  if (forReplace)
230  {
231  setButtonOK(KGuiItem( i18n("&Replace"), TQString::null,
232  i18n("Start replace"),
233  i18n("<qt>If you press the <b>Replace</b> button, the text you entered "
234  "above is searched for within the document and any occurrence is "
235  "replaced with the replacement text.</qt>")));
236  }
237  else
238  {
239  setButtonOK(KGuiItem( i18n("&Find"), "find",
240  i18n("Start searching"),
241  i18n("<qt>If you press the <b>Find</b> button, the text you entered "
242  "above is searched for within the document.</qt>")));
243  }
244 
245  // QWhatsthis texts
246  TQWhatsThis::add ( m_find, i18n(
247  "Enter a pattern to search for, or select a previous pattern from "
248  "the list.") );
249  TQWhatsThis::add ( m_regExp, i18n(
250  "If enabled, search for a regular expression.") );
251  TQWhatsThis::add ( m_regExpItem, i18n(
252  "Click here to edit your regular expression using a graphical editor.") );
253  TQWhatsThis::add ( m_replace, i18n(
254  "Enter a replacement string, or select a previous one from the list.") );
255  TQWhatsThis::add( m_backRef, i18n(
256  "<qt>If enabled, any occurrence of <code><b>\\N</b></code>, where "
257  "<code><b>N</b></code> is a integer number, will be replaced with "
258  "the corresponding capture (\"parenthesized substring\") from the "
259  "pattern.<p>To include (a literal <code><b>\\N</b></code> in your "
260  "replacement, put an extra backslash in front of it, like "
261  "<code><b>\\\\N</b></code>.</qt>") );
262  TQWhatsThis::add ( m_backRefItem, i18n(
263  "Click for a menu of available captures.") );
264  TQWhatsThis::add ( m_wholeWordsOnly, i18n(
265  "Require word boundaries in both ends of a match to succeed.") );
266  TQWhatsThis::add ( m_fromCursor, i18n(
267  "Start searching at the current cursor location rather than at the top.") );
268  TQWhatsThis::add ( m_selectedText, i18n(
269  "Only search within the current selection.") );
270  TQWhatsThis::add ( m_caseSensitive, i18n(
271  "Perform a case sensitive search: entering the pattern "
272  "'Joe' will not match 'joe' or 'JOE', only 'Joe'.") );
273  TQWhatsThis::add ( m_findBackwards, i18n(
274  "Search backwards.") );
275  TQWhatsThis::add ( m_promptOnReplace, i18n(
276  "Ask before replacing each match found.") );
277 }
278 
279 void KFindDialog::textSearchChanged( const TQString & text)
280 {
281  enableButtonOK( !text.isEmpty() );
282 }
283 
284 void KFindDialog::showEvent( TQShowEvent *e )
285 {
286  if ( !d->m_initialShowDone )
287  {
288  d->m_initialShowDone = true; // only once
289  kdDebug() << "showEvent\n";
290  if (!d->findStrings.isEmpty())
291  setFindHistory(d->findStrings);
292  d->findStrings = TQStringList();
293  if (!d->pattern.isEmpty()) {
294  m_find->lineEdit()->setText( d->pattern );
295  m_find->lineEdit()->selectAll();
296  d->pattern = TQString::null;
297  }
298  }
299  KDialogBase::showEvent(e);
300 }
301 
302 long KFindDialog::options() const
303 {
304  long options = 0;
305 
306  if (m_caseSensitive->isChecked())
307  options |= CaseSensitive;
308  if (m_wholeWordsOnly->isChecked())
309  options |= WholeWordsOnly;
310  if (m_fromCursor->isChecked())
311  options |= FromCursor;
312  if (m_findBackwards->isChecked())
313  options |= FindBackwards;
314  if (m_selectedText->isChecked())
315  options |= SelectedText;
316  if (m_regExp->isChecked())
317  options |= RegularExpression;
318  return options;
319 }
320 
321 TQString KFindDialog::pattern() const
322 {
323  return m_find->currentText();
324 }
325 
326 void KFindDialog::setPattern (const TQString &pattern)
327 {
328  m_find->lineEdit()->setText( pattern );
329  m_find->lineEdit()->selectAll();
330  d->pattern = pattern;
331  kdDebug() << "setPattern " << pattern<<endl;
332 }
333 
334 void KFindDialog::setFindHistory(const TQStringList &strings)
335 {
336  if (strings.count() > 0)
337  {
338  m_find->setHistoryItems(strings, true);
339  m_find->lineEdit()->setText( strings.first() );
340  m_find->lineEdit()->selectAll();
341  }
342  else
343  m_find->clearHistory();
344 }
345 
346 void KFindDialog::setHasSelection(bool hasSelection)
347 {
348  if (hasSelection) d->m_enabled |= SelectedText;
349  else d->m_enabled &= ~SelectedText;
350  m_selectedText->setEnabled( hasSelection );
351  if ( !hasSelection )
352  {
353  m_selectedText->setChecked( false );
354  slotSelectedTextToggled( hasSelection );
355  }
356 }
357 
358 void KFindDialog::slotSelectedTextToggled(bool selec)
359 {
360  // From cursor doesn't make sense if we have a selection
361  m_fromCursor->setEnabled( !selec && (d->m_enabled & FromCursor) );
362  if ( selec ) // uncheck if disabled
363  m_fromCursor->setChecked( false );
364 }
365 
366 void KFindDialog::setHasCursor(bool hasCursor)
367 {
368  if (hasCursor) d->m_enabled |= FromCursor;
369  else d->m_enabled &= ~FromCursor;
370  m_fromCursor->setEnabled( hasCursor );
371  m_fromCursor->setChecked( hasCursor && (options() & FromCursor) );
372 }
373 
374 void KFindDialog::setSupportsBackwardsFind( bool supports )
375 {
376  // ########## Shouldn't this hide the checkbox instead?
377  if (supports) d->m_enabled |= FindBackwards;
378  else d->m_enabled &= ~FindBackwards;
379  m_findBackwards->setEnabled( supports );
380  m_findBackwards->setChecked( supports && (options() & FindBackwards) );
381 }
382 
383 void KFindDialog::setSupportsCaseSensitiveFind( bool supports )
384 {
385  // ########## This should hide the checkbox instead
386  if (supports) d->m_enabled |= CaseSensitive;
387  else d->m_enabled &= ~CaseSensitive;
388  m_caseSensitive->setEnabled( supports );
389  m_caseSensitive->setChecked( supports && (options() & CaseSensitive) );
390 }
391 
392 void KFindDialog::setSupportsWholeWordsFind( bool supports )
393 {
394  // ########## This should hide the checkbox instead
395  if (supports) d->m_enabled |= WholeWordsOnly;
396  else d->m_enabled &= ~WholeWordsOnly;
397  m_wholeWordsOnly->setEnabled( supports );
398  m_wholeWordsOnly->setChecked( supports && (options() & WholeWordsOnly) );
399 }
400 
401 void KFindDialog::setSupportsRegularExpressionFind( bool supports )
402 {
403  // ########## This should hide the checkbox instead
404  if (supports) d->m_enabled |= RegularExpression;
405  else d->m_enabled &= ~RegularExpression;
406  m_regExp->setEnabled( supports );
407  m_regExp->setChecked( supports && (options() & RegularExpression) );
408 }
409 
410 void KFindDialog::setOptions(long options)
411 {
412  m_caseSensitive->setChecked((d->m_enabled & CaseSensitive) && (options & CaseSensitive));
413  m_wholeWordsOnly->setChecked((d->m_enabled & WholeWordsOnly) && (options & WholeWordsOnly));
414  m_fromCursor->setChecked((d->m_enabled & FromCursor) && (options & FromCursor));
415  m_findBackwards->setChecked((d->m_enabled & FindBackwards) && (options & FindBackwards));
416  m_selectedText->setChecked((d->m_enabled & SelectedText) && (options & SelectedText));
417  m_regExp->setChecked((d->m_enabled & RegularExpression) && (options & RegularExpression));
418 }
419 
420 // Create a popup menu with a list of regular expression terms, to help the user
421 // compose a regular expression search pattern.
422 void KFindDialog::showPatterns()
423 {
424  if ( !d->m_regexpDialogQueryDone )
425  {
426  d->m_regexpDialog = KParts::ComponentFactory::createInstanceFromQuery<TQDialog>( "KRegExpEditor/KRegExpEditor", TQString(), TQT_TQOBJECT(this) );
427  d->m_regexpDialogQueryDone = true;
428  }
429 
430  if ( d->m_regexpDialog )
431  {
432  KRegExpEditorInterface *iface = tqt_dynamic_cast<KRegExpEditorInterface *>( d->m_regexpDialog );
433  assert( iface );
434 
435  iface->setRegExp( pattern() );
436  if ( d->m_regexpDialog->exec() == TQDialog::Accepted )
437  setPattern( iface->regExp() );
438  }
439  else // No complete regexp-editor available, bring up the old popupmenu
440  {
441  typedef struct
442  {
443  const char *description;
444  const char *regExp;
445  int cursorAdjustment;
446  } term;
447  static const term items[] =
448  {
449  { I18N_NOOP("Any Character"), ".", 0 },
450  { I18N_NOOP("Start of Line"), "^", 0 },
451  { I18N_NOOP("End of Line"), "$", 0 },
452  { I18N_NOOP("Set of Characters"), "[]", -1 },
453  { I18N_NOOP("Repeats, Zero or More Times"), "*", 0 },
454  { I18N_NOOP("Repeats, One or More Times"), "+", 0 },
455  { I18N_NOOP("Optional"), "?", 0 },
456  { I18N_NOOP("Escape"), "\\", 0 },
457  { I18N_NOOP("TAB"), "\\t", 0 },
458  { I18N_NOOP("Newline"), "\\n", 0 },
459  { I18N_NOOP("Carriage Return"), "\\r", 0 },
460  { I18N_NOOP("White Space"), "\\s", 0 },
461  { I18N_NOOP("Digit"), "\\d", 0 },
462  };
463  int i;
464 
465  // Populate the popup menu.
466  if (!m_patterns)
467  {
468  m_patterns = new TQPopupMenu(this);
469  for (i = 0; (unsigned)i < sizeof(items) / sizeof(items[0]); i++)
470  {
471  m_patterns->insertItem(i18n(items[i].description), i, i);
472  }
473  }
474 
475  // Insert the selection into the edit control.
476  i = m_patterns->exec(m_regExpItem->mapToGlobal(m_regExpItem->rect().bottomLeft()));
477  if (i != -1)
478  {
479  TQLineEdit *editor = m_find->lineEdit();
480 
481  editor->insert(items[i].regExp);
482  editor->setCursorPosition(editor->cursorPosition() + items[i].cursorAdjustment);
483  }
484  }
485 }
486 
487 // Create a popup menu with a list of backreference terms, to help the user
488 // compose a regular expression replacement pattern.
489 void KFindDialog::showPlaceholders()
490 {
491  // Populate the popup menu.
492  if (!m_placeholders)
493  {
494  m_placeholders = new TQPopupMenu(this);
495  connect( m_placeholders, TQT_SIGNAL(aboutToShow()), this, TQT_SLOT(slotPlaceholdersAboutToShow()) );
496  }
497 
498  // Insert the selection into the edit control.
499  int i = m_placeholders->exec(m_backRefItem->mapToGlobal(m_backRefItem->rect().bottomLeft()));
500  if (i != -1)
501  {
502  TQLineEdit *editor = m_replace->lineEdit();
503  editor->insert( TQString("\\%1").arg( i ) );
504  }
505 }
506 
507 void KFindDialog::slotPlaceholdersAboutToShow()
508 {
509  m_placeholders->clear();
510  m_placeholders->insertItem( i18n("Complete Match"), 0 );
511 
512  TQRegExp r( pattern() );
513  uint n = r.numCaptures();
514  for ( uint i=0; i < n; i++ )
515  m_placeholders->insertItem( i18n("Captured Text (%1)").arg( i+1 ), i+1 );
516 }
517 
518 void KFindDialog::slotOk()
519 {
520  // Nothing to find?
521  if (pattern().isEmpty())
522  {
523  KMessageBox::error(this, i18n("You must enter some text to search for."));
524  return;
525  }
526 
527  if (m_regExp->isChecked())
528  {
529  // Check for a valid regular expression.
530  TQRegExp regExp(pattern());
531 
532  if (!regExp.isValid())
533  {
534  KMessageBox::error(this, i18n("Invalid regular expression."));
535  return;
536  }
537  }
538  m_find->addToHistory(pattern());
539  emit okClicked();
540  if ( testWFlags( WShowModal ) )
541  accept();
542 }
543 // kate: space-indent on; indent-width 4; replace-tabs on;
544 #include "kfinddialog.moc"

kutils

Skip menu "kutils"
  • Main Page
  • Modules
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Class Members
  • Related Pages

kutils

Skip menu "kutils"
  • arts
  • dcop
  • dnssd
  • interfaces
  •     interface
  •     library
  •   kspeech
  •   ktexteditor
  • kabc
  • kate
  • kcmshell
  • kdecore
  • kded
  • kdefx
  • kdeprint
  • kdesu
  • kdeui
  • kdoctools
  • khtml
  • kimgio
  • kinit
  • kio
  •   bookmarks
  •   httpfilter
  •   kfile
  •   kio
  •   kioexec
  •   kpasswdserver
  •   kssl
  • kioslave
  •   http
  • kjs
  • kmdi
  •   kmdi
  • knewstuff
  • kparts
  • krandr
  • kresources
  • kspell2
  • kunittest
  • kutils
  • kwallet
  • libkmid
  • libkscreensaver
Generated for kutils by doxygen 1.8.3.1
This website is maintained by Timothy Pearson.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. |