kfinddialog.cpp
00001 /* 00002 Copyright (C) 2001, S.R.Haque <srhaque@iee.org>. 00003 Copyright (C) 2002, David Faure <david@mandrakesoft.com> 00004 This file is part of the KDE project 00005 00006 This library is free software; you can redistribute it and/or 00007 modify it under the terms of the GNU Library General Public 00008 License version 2, as published by the Free Software Foundation. 00009 00010 This library is distributed in the hope that it will be useful, 00011 but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00013 Library General Public License for more details. 00014 00015 You should have received a copy of the GNU Library General Public License 00016 along with this library; see the file COPYING.LIB. If not, write to 00017 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00018 Boston, MA 02110-1301, USA. 00019 */ 00020 00021 #include "kfinddialog.h" 00022 #include <qcheckbox.h> 00023 #include <qcursor.h> 00024 #include <qgroupbox.h> 00025 #include <qlabel.h> 00026 #include <qlayout.h> 00027 #include <qpopupmenu.h> 00028 #include <qpushbutton.h> 00029 #include <qregexp.h> 00030 #include <kcombobox.h> 00031 #include <kdebug.h> 00032 #include <klocale.h> 00033 #include <kmessagebox.h> 00034 #include <assert.h> 00035 #include <qwhatsthis.h> 00036 00037 #include <kregexpeditorinterface.h> 00038 #include <kparts/componentfactory.h> 00039 00040 class KFindDialog::KFindDialogPrivate 00041 { 00042 public: 00043 KFindDialogPrivate() : m_regexpDialog(0), 00044 m_regexpDialogQueryDone(false), 00045 m_enabled(WholeWordsOnly | FromCursor | SelectedText | CaseSensitive | FindBackwards | RegularExpression), m_initialShowDone(false) {} 00046 QDialog* m_regexpDialog; 00047 bool m_regexpDialogQueryDone; 00048 long m_enabled; // uses Options to define which search options are enabled 00049 bool m_initialShowDone; 00050 QStringList findStrings; 00051 QString pattern; 00052 }; 00053 00054 KFindDialog::KFindDialog(QWidget *parent, const char *name, long options, const QStringList &findStrings, bool hasSelection) : 00055 KDialogBase(parent, name, true, i18n("Find Text"), Ok | Cancel, Ok), 00056 m_findExtension (0), 00057 m_replaceExtension (0) 00058 { 00059 d = new KFindDialogPrivate; 00060 init(false, findStrings, hasSelection); 00061 setOptions(options); 00062 setButtonCancel( KStdGuiItem::close() ); 00063 } 00064 00065 KFindDialog::KFindDialog(bool modal, QWidget *parent, const char *name, long options, const QStringList &findStrings, bool hasSelection) : 00066 KDialogBase(parent, name, modal, i18n("Find Text"), Ok | Cancel, Ok), 00067 m_findExtension (0), 00068 m_replaceExtension (0) 00069 { 00070 d = new KFindDialogPrivate; 00071 init(false, findStrings, hasSelection); 00072 setOptions(options); 00073 setButtonCancel( KStdGuiItem::close() ); 00074 } 00075 00076 KFindDialog::KFindDialog(QWidget *parent, const char *name, bool /*forReplace*/) : 00077 KDialogBase(parent, name, true, i18n("Replace Text"), Ok | Cancel, Ok), 00078 m_findExtension (0), 00079 m_replaceExtension (0) 00080 { 00081 d = new KFindDialogPrivate; 00082 setButtonCancel( KStdGuiItem::close() ); 00083 } 00084 00085 KFindDialog::~KFindDialog() 00086 { 00087 delete d; 00088 } 00089 00090 QWidget *KFindDialog::findExtension() 00091 { 00092 if (!m_findExtension) 00093 { 00094 m_findExtension = new QWidget(m_findGrp); 00095 m_findLayout->addMultiCellWidget(m_findExtension, 3, 3, 0, 1); 00096 } 00097 00098 return m_findExtension; 00099 } 00100 00101 QStringList KFindDialog::findHistory() const 00102 { 00103 return m_find->historyItems(); 00104 } 00105 00106 void KFindDialog::init(bool forReplace, const QStringList &findStrings, bool hasSelection) 00107 { 00108 QVBoxLayout *topLayout; 00109 QGridLayout *optionsLayout; 00110 00111 // Create common parts of dialog. 00112 QWidget *page = new QWidget(this); 00113 setMainWidget(page); 00114 00115 topLayout = new QVBoxLayout(page); 00116 topLayout->setSpacing( KDialog::spacingHint() ); 00117 topLayout->setMargin( 0 ); 00118 00119 m_findGrp = new QGroupBox(0, Qt::Vertical, i18n("Find"), page); 00120 m_findGrp->layout()->setSpacing( KDialog::spacingHint() ); 00121 // m_findGrp->layout()->setMargin( KDialog::marginHint() ); 00122 m_findLayout = new QGridLayout(m_findGrp->layout()); 00123 m_findLayout->setSpacing( KDialog::spacingHint() ); 00124 // m_findLayout->setMargin( KDialog::marginHint() ); 00125 00126 m_findLabel = new QLabel(i18n("&Text to find:"), m_findGrp); 00127 m_find = new KHistoryCombo(true, m_findGrp); 00128 m_find->setMaxCount(10); 00129 m_find->setDuplicatesEnabled(false); 00130 m_regExp = new QCheckBox(i18n("Regular e&xpression"), m_findGrp); 00131 m_regExpItem = new QPushButton(i18n("&Edit..."), m_findGrp); 00132 m_regExpItem->setEnabled(false); 00133 00134 m_findLayout->addWidget(m_findLabel, 0, 0); 00135 m_findLayout->addMultiCellWidget(m_find, 1, 1, 0, 1); 00136 m_findLayout->addWidget(m_regExp, 2, 0); 00137 m_findLayout->addWidget(m_regExpItem, 2, 1); 00138 topLayout->addWidget(m_findGrp); 00139 00140 m_replaceGrp = new QGroupBox(0, Qt::Vertical, i18n("Replace With"), page); 00141 m_replaceGrp->layout()->setSpacing( KDialog::spacingHint() ); 00142 // m_replaceGrp->layout()->setMargin( KDialog::marginHint() ); 00143 m_replaceLayout = new QGridLayout(m_replaceGrp->layout()); 00144 m_replaceLayout->setSpacing( KDialog::spacingHint() ); 00145 // m_replaceLayout->setMargin( KDialog::marginHint() ); 00146 00147 m_replaceLabel = new QLabel(i18n("Replace&ment text:"), m_replaceGrp); 00148 m_replace = new KHistoryCombo(true, m_replaceGrp); 00149 m_replace->setMaxCount(10); 00150 m_replace->setDuplicatesEnabled(false); 00151 m_backRef = new QCheckBox(i18n("Use p&laceholders"), m_replaceGrp); 00152 m_backRefItem = new QPushButton(i18n("Insert Place&holder"), m_replaceGrp); 00153 m_backRefItem->setEnabled(false); 00154 00155 m_replaceLayout->addWidget(m_replaceLabel, 0, 0); 00156 m_replaceLayout->addMultiCellWidget(m_replace, 1, 1, 0, 1); 00157 m_replaceLayout->addWidget(m_backRef, 2, 0); 00158 m_replaceLayout->addWidget(m_backRefItem, 2, 1); 00159 topLayout->addWidget(m_replaceGrp); 00160 00161 m_optionGrp = new QGroupBox(0, Qt::Vertical, i18n("Options"), page); 00162 m_optionGrp->layout()->setSpacing(KDialog::spacingHint()); 00163 // m_optionGrp->layout()->setMargin(KDialog::marginHint()); 00164 optionsLayout = new QGridLayout(m_optionGrp->layout()); 00165 optionsLayout->setSpacing( KDialog::spacingHint() ); 00166 // optionsLayout->setMargin( KDialog::marginHint() ); 00167 00168 m_caseSensitive = new QCheckBox(i18n("C&ase sensitive"), m_optionGrp); 00169 m_wholeWordsOnly = new QCheckBox(i18n("&Whole words only"), m_optionGrp); 00170 m_fromCursor = new QCheckBox(i18n("From c&ursor"), m_optionGrp); 00171 m_findBackwards = new QCheckBox(i18n("Find &backwards"), m_optionGrp); 00172 m_selectedText = new QCheckBox(i18n("&Selected text"), m_optionGrp); 00173 setHasSelection( hasSelection ); 00174 // If we have a selection, we make 'find in selection' default 00175 // and if we don't, then the option has to be unchecked, obviously. 00176 m_selectedText->setChecked( hasSelection ); 00177 slotSelectedTextToggled( hasSelection ); 00178 00179 m_promptOnReplace = new QCheckBox(i18n("&Prompt on replace"), m_optionGrp); 00180 m_promptOnReplace->setChecked( true ); 00181 00182 optionsLayout->addWidget(m_caseSensitive, 0, 0); 00183 optionsLayout->addWidget(m_wholeWordsOnly, 1, 0); 00184 optionsLayout->addWidget(m_fromCursor, 2, 0); 00185 optionsLayout->addWidget(m_findBackwards, 0, 1); 00186 optionsLayout->addWidget(m_selectedText, 1, 1); 00187 optionsLayout->addWidget(m_promptOnReplace, 2, 1); 00188 topLayout->addWidget(m_optionGrp); 00189 00190 // We delay creation of these until needed. 00191 m_patterns = 0L; 00192 m_placeholders = 0L; 00193 00194 // signals and slots connections 00195 connect(m_selectedText, SIGNAL(toggled(bool)), this, SLOT(slotSelectedTextToggled(bool))); 00196 connect(m_regExp, SIGNAL(toggled(bool)), m_regExpItem, SLOT(setEnabled(bool))); 00197 connect(m_backRef, SIGNAL(toggled(bool)), m_backRefItem, SLOT(setEnabled(bool))); 00198 connect(m_regExpItem, SIGNAL(clicked()), this, SLOT(showPatterns())); 00199 connect(m_backRefItem, SIGNAL(clicked()), this, SLOT(showPlaceholders())); 00200 00201 connect(m_find, SIGNAL(textChanged ( const QString & )),this, SLOT(textSearchChanged( const QString & ))); 00202 00203 // tab order 00204 setTabOrder(m_find, m_regExp); 00205 setTabOrder(m_regExp, m_regExpItem); 00206 setTabOrder(m_regExpItem, m_replace); 00207 setTabOrder(m_replace, m_backRef); 00208 setTabOrder(m_backRef, m_backRefItem); 00209 setTabOrder(m_backRefItem, m_caseSensitive); 00210 setTabOrder(m_caseSensitive, m_wholeWordsOnly); 00211 setTabOrder(m_wholeWordsOnly, m_fromCursor); 00212 setTabOrder(m_fromCursor, m_findBackwards); 00213 setTabOrder(m_findBackwards, m_selectedText); 00214 setTabOrder(m_selectedText, m_promptOnReplace); 00215 00216 // buddies 00217 m_findLabel->setBuddy(m_find); 00218 m_replaceLabel->setBuddy(m_replace); 00219 00220 if (!forReplace) 00221 { 00222 m_promptOnReplace->hide(); 00223 m_replaceGrp->hide(); 00224 } 00225 00226 d->findStrings = findStrings; 00227 m_find->setFocus(); 00228 enableButtonOK( !pattern().isEmpty() ); 00229 if (forReplace) 00230 { 00231 setButtonOK(KGuiItem( i18n("&Replace"), QString::null, 00232 i18n("Start replace"), 00233 i18n("<qt>If you press the <b>Replace</b> button, the text you entered " 00234 "above is searched for within the document and any occurrence is " 00235 "replaced with the replacement text.</qt>"))); 00236 } 00237 else 00238 { 00239 setButtonOK(KGuiItem( i18n("&Find"), "find", 00240 i18n("Start searching"), 00241 i18n("<qt>If you press the <b>Find</b> button, the text you entered " 00242 "above is searched for within the document.</qt>"))); 00243 } 00244 00245 // QWhatsthis texts 00246 QWhatsThis::add ( m_find, i18n( 00247 "Enter a pattern to search for, or select a previous pattern from " 00248 "the list.") ); 00249 QWhatsThis::add ( m_regExp, i18n( 00250 "If enabled, search for a regular expression.") ); 00251 QWhatsThis::add ( m_regExpItem, i18n( 00252 "Click here to edit your regular expression using a graphical editor.") ); 00253 QWhatsThis::add ( m_replace, i18n( 00254 "Enter a replacement string, or select a previous one from the list.") ); 00255 QWhatsThis::add( m_backRef, i18n( 00256 "<qt>If enabled, any occurrence of <code><b>\\N</b></code>, where " 00257 "<code><b>N</b></code> is a integer number, will be replaced with " 00258 "the corresponding capture (\"parenthesized substring\") from the " 00259 "pattern.<p>To include (a literal <code><b>\\N</b></code> in your " 00260 "replacement, put an extra backslash in front of it, like " 00261 "<code><b>\\\\N</b></code>.</qt>") ); 00262 QWhatsThis::add ( m_backRefItem, i18n( 00263 "Click for a menu of available captures.") ); 00264 QWhatsThis::add ( m_wholeWordsOnly, i18n( 00265 "Require word boundaries in both ends of a match to succeed.") ); 00266 QWhatsThis::add ( m_fromCursor, i18n( 00267 "Start searching at the current cursor location rather than at the top.") ); 00268 QWhatsThis::add ( m_selectedText, i18n( 00269 "Only search within the current selection.") ); 00270 QWhatsThis::add ( m_caseSensitive, i18n( 00271 "Perform a case sensitive search: entering the pattern " 00272 "'Joe' will not match 'joe' or 'JOE', only 'Joe'.") ); 00273 QWhatsThis::add ( m_findBackwards, i18n( 00274 "Search backwards.") ); 00275 QWhatsThis::add ( m_promptOnReplace, i18n( 00276 "Ask before replacing each match found.") ); 00277 } 00278 00279 void KFindDialog::textSearchChanged( const QString & text) 00280 { 00281 enableButtonOK( !text.isEmpty() ); 00282 } 00283 00284 void KFindDialog::showEvent( QShowEvent *e ) 00285 { 00286 if ( !d->m_initialShowDone ) 00287 { 00288 d->m_initialShowDone = true; // only once 00289 kdDebug() << "showEvent\n"; 00290 if (!d->findStrings.isEmpty()) 00291 setFindHistory(d->findStrings); 00292 d->findStrings = QStringList(); 00293 if (!d->pattern.isEmpty()) { 00294 m_find->lineEdit()->setText( d->pattern ); 00295 m_find->lineEdit()->selectAll(); 00296 d->pattern = QString::null; 00297 } 00298 } 00299 KDialogBase::showEvent(e); 00300 } 00301 00302 long KFindDialog::options() const 00303 { 00304 long options = 0; 00305 00306 if (m_caseSensitive->isChecked()) 00307 options |= CaseSensitive; 00308 if (m_wholeWordsOnly->isChecked()) 00309 options |= WholeWordsOnly; 00310 if (m_fromCursor->isChecked()) 00311 options |= FromCursor; 00312 if (m_findBackwards->isChecked()) 00313 options |= FindBackwards; 00314 if (m_selectedText->isChecked()) 00315 options |= SelectedText; 00316 if (m_regExp->isChecked()) 00317 options |= RegularExpression; 00318 return options; 00319 } 00320 00321 QString KFindDialog::pattern() const 00322 { 00323 return m_find->currentText(); 00324 } 00325 00326 void KFindDialog::setPattern (const QString &pattern) 00327 { 00328 m_find->lineEdit()->setText( pattern ); 00329 m_find->lineEdit()->selectAll(); 00330 d->pattern = pattern; 00331 kdDebug() << "setPattern " << pattern<<endl; 00332 } 00333 00334 void KFindDialog::setFindHistory(const QStringList &strings) 00335 { 00336 if (strings.count() > 0) 00337 { 00338 m_find->setHistoryItems(strings, true); 00339 m_find->lineEdit()->setText( strings.first() ); 00340 m_find->lineEdit()->selectAll(); 00341 } 00342 else 00343 m_find->clearHistory(); 00344 } 00345 00346 void KFindDialog::setHasSelection(bool hasSelection) 00347 { 00348 if (hasSelection) d->m_enabled |= SelectedText; 00349 else d->m_enabled &= ~SelectedText; 00350 m_selectedText->setEnabled( hasSelection ); 00351 if ( !hasSelection ) 00352 { 00353 m_selectedText->setChecked( false ); 00354 slotSelectedTextToggled( hasSelection ); 00355 } 00356 } 00357 00358 void KFindDialog::slotSelectedTextToggled(bool selec) 00359 { 00360 // From cursor doesn't make sense if we have a selection 00361 m_fromCursor->setEnabled( !selec && (d->m_enabled & FromCursor) ); 00362 if ( selec ) // uncheck if disabled 00363 m_fromCursor->setChecked( false ); 00364 } 00365 00366 void KFindDialog::setHasCursor(bool hasCursor) 00367 { 00368 if (hasCursor) d->m_enabled |= FromCursor; 00369 else d->m_enabled &= ~FromCursor; 00370 m_fromCursor->setEnabled( hasCursor ); 00371 m_fromCursor->setChecked( hasCursor && (options() & FromCursor) ); 00372 } 00373 00374 void KFindDialog::setSupportsBackwardsFind( bool supports ) 00375 { 00376 // ########## Shouldn't this hide the checkbox instead? 00377 if (supports) d->m_enabled |= FindBackwards; 00378 else d->m_enabled &= ~FindBackwards; 00379 m_findBackwards->setEnabled( supports ); 00380 m_findBackwards->setChecked( supports && (options() & FindBackwards) ); 00381 } 00382 00383 void KFindDialog::setSupportsCaseSensitiveFind( bool supports ) 00384 { 00385 // ########## This should hide the checkbox instead 00386 if (supports) d->m_enabled |= CaseSensitive; 00387 else d->m_enabled &= ~CaseSensitive; 00388 m_caseSensitive->setEnabled( supports ); 00389 m_caseSensitive->setChecked( supports && (options() & CaseSensitive) ); 00390 } 00391 00392 void KFindDialog::setSupportsWholeWordsFind( bool supports ) 00393 { 00394 // ########## This should hide the checkbox instead 00395 if (supports) d->m_enabled |= WholeWordsOnly; 00396 else d->m_enabled &= ~WholeWordsOnly; 00397 m_wholeWordsOnly->setEnabled( supports ); 00398 m_wholeWordsOnly->setChecked( supports && (options() & WholeWordsOnly) ); 00399 } 00400 00401 void KFindDialog::setSupportsRegularExpressionFind( bool supports ) 00402 { 00403 // ########## This should hide the checkbox instead 00404 if (supports) d->m_enabled |= RegularExpression; 00405 else d->m_enabled &= ~RegularExpression; 00406 m_regExp->setEnabled( supports ); 00407 m_regExp->setChecked( supports && (options() & RegularExpression) ); 00408 } 00409 00410 void KFindDialog::setOptions(long options) 00411 { 00412 m_caseSensitive->setChecked((d->m_enabled & CaseSensitive) && (options & CaseSensitive)); 00413 m_wholeWordsOnly->setChecked((d->m_enabled & WholeWordsOnly) && (options & WholeWordsOnly)); 00414 m_fromCursor->setChecked((d->m_enabled & FromCursor) && (options & FromCursor)); 00415 m_findBackwards->setChecked((d->m_enabled & FindBackwards) && (options & FindBackwards)); 00416 m_selectedText->setChecked((d->m_enabled & SelectedText) && (options & SelectedText)); 00417 m_regExp->setChecked((d->m_enabled & RegularExpression) && (options & RegularExpression)); 00418 } 00419 00420 // Create a popup menu with a list of regular expression terms, to help the user 00421 // compose a regular expression search pattern. 00422 void KFindDialog::showPatterns() 00423 { 00424 if ( !d->m_regexpDialogQueryDone ) 00425 { 00426 d->m_regexpDialog = KParts::ComponentFactory::createInstanceFromQuery<QDialog>( "KRegExpEditor/KRegExpEditor", QString::null, this ); 00427 d->m_regexpDialogQueryDone = true; 00428 } 00429 00430 if ( d->m_regexpDialog ) 00431 { 00432 KRegExpEditorInterface *iface = static_cast<KRegExpEditorInterface *>( d->m_regexpDialog->qt_cast( "KRegExpEditorInterface" ) ); 00433 assert( iface ); 00434 00435 iface->setRegExp( pattern() ); 00436 if ( d->m_regexpDialog->exec() == QDialog::Accepted ) 00437 setPattern( iface->regExp() ); 00438 } 00439 else // No complete regexp-editor available, bring up the old popupmenu 00440 { 00441 typedef struct 00442 { 00443 const char *description; 00444 const char *regExp; 00445 int cursorAdjustment; 00446 } term; 00447 static const term items[] = 00448 { 00449 { I18N_NOOP("Any Character"), ".", 0 }, 00450 { I18N_NOOP("Start of Line"), "^", 0 }, 00451 { I18N_NOOP("End of Line"), "$", 0 }, 00452 { I18N_NOOP("Set of Characters"), "[]", -1 }, 00453 { I18N_NOOP("Repeats, Zero or More Times"), "*", 0 }, 00454 { I18N_NOOP("Repeats, One or More Times"), "+", 0 }, 00455 { I18N_NOOP("Optional"), "?", 0 }, 00456 { I18N_NOOP("Escape"), "\\", 0 }, 00457 { I18N_NOOP("TAB"), "\\t", 0 }, 00458 { I18N_NOOP("Newline"), "\\n", 0 }, 00459 { I18N_NOOP("Carriage Return"), "\\r", 0 }, 00460 { I18N_NOOP("White Space"), "\\s", 0 }, 00461 { I18N_NOOP("Digit"), "\\d", 0 }, 00462 }; 00463 int i; 00464 00465 // Populate the popup menu. 00466 if (!m_patterns) 00467 { 00468 m_patterns = new QPopupMenu(this); 00469 for (i = 0; (unsigned)i < sizeof(items) / sizeof(items[0]); i++) 00470 { 00471 m_patterns->insertItem(i18n(items[i].description), i, i); 00472 } 00473 } 00474 00475 // Insert the selection into the edit control. 00476 i = m_patterns->exec(m_regExpItem->mapToGlobal(m_regExpItem->rect().bottomLeft())); 00477 if (i != -1) 00478 { 00479 QLineEdit *editor = m_find->lineEdit(); 00480 00481 editor->insert(items[i].regExp); 00482 editor->setCursorPosition(editor->cursorPosition() + items[i].cursorAdjustment); 00483 } 00484 } 00485 } 00486 00487 // Create a popup menu with a list of backreference terms, to help the user 00488 // compose a regular expression replacement pattern. 00489 void KFindDialog::showPlaceholders() 00490 { 00491 // Populate the popup menu. 00492 if (!m_placeholders) 00493 { 00494 m_placeholders = new QPopupMenu(this); 00495 connect( m_placeholders, SIGNAL(aboutToShow()), this, SLOT(slotPlaceholdersAboutToShow()) ); 00496 } 00497 00498 // Insert the selection into the edit control. 00499 int i = m_placeholders->exec(m_backRefItem->mapToGlobal(m_backRefItem->rect().bottomLeft())); 00500 if (i != -1) 00501 { 00502 QLineEdit *editor = m_replace->lineEdit(); 00503 editor->insert( QString("\\%1").arg( i ) ); 00504 } 00505 } 00506 00507 void KFindDialog::slotPlaceholdersAboutToShow() 00508 { 00509 m_placeholders->clear(); 00510 m_placeholders->insertItem( i18n("Complete Match"), 0 ); 00511 00512 QRegExp r( pattern() ); 00513 uint n = r.numCaptures(); 00514 for ( uint i=0; i < n; i++ ) 00515 m_placeholders->insertItem( i18n("Captured Text (%1)").arg( i+1 ), i+1 ); 00516 } 00517 00518 void KFindDialog::slotOk() 00519 { 00520 // Nothing to find? 00521 if (pattern().isEmpty()) 00522 { 00523 KMessageBox::error(this, i18n("You must enter some text to search for.")); 00524 return; 00525 } 00526 00527 if (m_regExp->isChecked()) 00528 { 00529 // Check for a valid regular expression. 00530 QRegExp regExp(pattern()); 00531 00532 if (!regExp.isValid()) 00533 { 00534 KMessageBox::error(this, i18n("Invalid regular expression.")); 00535 return; 00536 } 00537 } 00538 m_find->addToHistory(pattern()); 00539 emit okClicked(); 00540 if ( testWFlags( WShowModal ) ) 00541 accept(); 00542 } 00543 // kate: space-indent on; indent-width 4; replace-tabs on; 00544 #include "kfinddialog.moc"