source: trunk/src/qt3support/dialogs/q3filedialog_mac.cpp

Last change on this file was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 22.1 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation (qt-info@nokia.com)
6**
7** This file is part of the Qt3Support module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at qt-info@nokia.com.
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "q3filedialog.h"
43
44#ifndef QT_NO_FILEDIALOG
45
46/*****************************************************************************
47 Q3FileDialog debug facilities
48 *****************************************************************************/
49//#define DEBUG_FILEDIALOG_FILTERS
50
51#include "qapplication.h"
52#include <private/qapplication_p.h>
53#include <private/qt_mac_p.h>
54#include "qregexp.h"
55#include "qbuffer.h"
56#include "qstringlist.h"
57#include "qtextcodec.h"
58#include "qdesktopwidget.h"
59#include "qfiledialog.h"
60#include <stdlib.h>
61
62QT_BEGIN_NAMESPACE
63
64#ifdef QT_MAC_USE_COCOA
65
66QStringList Q3FileDialog::macGetOpenFileNames(const QString &filter, QString *pwd,
67 QWidget *parent, const char* /*name*/,
68 const QString& caption, QString *selectedFilter,
69 bool /*multi*/, bool /*directory*/)
70{
71 return QFileDialog::getOpenFileNames(filter, *pwd, parent, 0,
72 caption);
73}
74
75
76QString Q3FileDialog::macGetSaveFileName(const QString &start, const QString &filter,
77 QString *, QWidget *parent, const char* /*name*/,
78 const QString& caption, QString *selectedFilter)
79{
80 return QFileDialog::getSaveFileName(start, filter, parent, 0,
81 caption, selectedFilter);
82}
83
84#else
85
86/*****************************************************************************
87 Externals
88 *****************************************************************************/
89extern WindowPtr qt_mac_window_for(const QWidget*); //qwidget_mac.cpp
90extern const char qt3_file_dialog_filter_reg_exp[]; //qfiledialog.cpp
91
92/*****************************************************************************
93 Q3FileDialog utility functions
94 *****************************************************************************/
95static UInt8 *str_buffer = NULL;
96static void cleanup_str_buffer()
97{
98 if(str_buffer) {
99 free(str_buffer);
100 str_buffer = NULL;
101 }
102}
103
104// Returns the wildcard part of a filter.
105struct qt_mac_filter_name {
106 QString description, regxp, filter;
107};
108static qt_mac_filter_name *extractFilter(const QString& rawFilter)
109{
110 qt_mac_filter_name *ret = new qt_mac_filter_name;
111 ret->filter = rawFilter;
112 QString result = rawFilter;
113 QRegExp r(QString::fromLatin1(qt3_file_dialog_filter_reg_exp));
114 int index = r.indexIn(result);
115 if(index >= 0) {
116 ret->description = r.cap(1).trimmed();
117 result = r.cap(2);
118 }
119 if(ret->description.isEmpty())
120 ret->description = result;
121 ret->regxp = result.replace(QLatin1Char(' '), QLatin1Char(';'));
122 return ret;
123}
124
125// Makes a list of filters from ;;-separated text.
126static QList<qt_mac_filter_name*> makeFiltersList(const QString &filter)
127{
128#ifdef DEBUG_FILEDIALOG_FILTERS
129 qDebug("Q3FileDialog:%d - Got filter (%s)", __LINE__, filter.latin1());
130#endif
131 QString f(filter);
132 if(f.isEmpty())
133 f = Q3FileDialog::tr("All Files (*)");
134 if(f.isEmpty())
135 return QList<qt_mac_filter_name*>();
136 QString sep(QLatin1String(";;"));
137 int i = f.indexOf(sep, 0);
138 if(i == -1) {
139 sep = QLatin1String("\n");
140 if(f.indexOf(sep, 0) != -1)
141 i = f.indexOf(sep, 0);
142 }
143
144 QList<qt_mac_filter_name*> ret;
145 QStringList filts = f.split(sep);
146 for (QStringList::Iterator it = filts.begin(); it != filts.end(); ++it) {
147 qt_mac_filter_name *filter = extractFilter((*it));
148#ifdef DEBUG_FILEDIALOG_FILTERS
149 qDebug("Q3FileDialog:%d Split out filter (%d) '%s' '%s'", __LINE__, ret.count(),
150 filter->regxp.latin1(), filter->description.latin1());
151#endif
152 ret.append(filter);
153 }
154 return ret;
155}
156
157struct qt_mac_nav_filter_type {
158 int index;
159 QList<qt_mac_filter_name*> *filts;
160};
161
162static Boolean qt_mac_nav_filter(AEDesc *theItem, void *info,
163 void *myd, NavFilterModes)
164{
165 qt_mac_nav_filter_type *t = (qt_mac_nav_filter_type *)myd;
166 if(!t || !t->filts || t->index >= t->filts->count())
167 return true;
168
169 NavFileOrFolderInfo *theInfo = (NavFileOrFolderInfo *)info;
170 QString file;
171 qt_mac_filter_name *fn = t->filts->at(t->index);
172 if(!fn)
173 return true;
174 if(theItem->descriptorType == typeFSRef) {
175 FSRef ref;
176 AEGetDescData(theItem, &ref, sizeof(ref));
177 if(!str_buffer) {
178 qAddPostRoutine(cleanup_str_buffer);
179 str_buffer = (UInt8 *)malloc(1024);
180 }
181 FSRefMakePath(&ref, str_buffer, 1024);
182 file = QString::fromUtf8((const char *)str_buffer);
183 int slsh = file.lastIndexOf(QLatin1Char('/'));
184 if(slsh != -1)
185 file = file.right(file.length() - slsh - 1);
186 }
187 QStringList reg = fn->regxp.split(QLatin1String(";"));
188 for(QStringList::Iterator it = reg.begin(); it != reg.end(); ++it) {
189 QRegExp rg((*it), false, true);
190#ifdef DEBUG_FILEDIALOG_FILTERS
191 qDebug("Q3FileDialog:%d, asked to filter.. %s (%s)", __LINE__,
192 file.latin1(), (*it).latin1());
193#endif
194 if(rg.exactMatch(file))
195 return true;
196 }
197 return (theInfo->isFolder && !file.endsWith(QLatin1String(".app")));
198}
199
200//filter UPP stuff
201static NavObjectFilterUPP mac_navFilterUPP = NULL;
202static void cleanup_navFilterUPP()
203{
204 DisposeNavObjectFilterUPP(mac_navFilterUPP);
205 mac_navFilterUPP = NULL;
206}
207static const NavObjectFilterUPP make_navFilterUPP()
208{
209 if(mac_navFilterUPP)
210 return mac_navFilterUPP;
211 qAddPostRoutine(cleanup_navFilterUPP);
212 return mac_navFilterUPP = NewNavObjectFilterUPP(qt_mac_nav_filter);
213}
214//event UPP stuff
215static NavEventUPP mac_navProcUPP = NULL;
216static void cleanup_navProcUPP()
217{
218 DisposeNavEventUPP(mac_navProcUPP);
219 mac_navProcUPP = NULL;
220}
221static bool g_nav_blocking=true;
222static void qt_mac_filedialog_event_proc(const NavEventCallbackMessage msg,
223 NavCBRecPtr p, NavCallBackUserData myd)
224{
225 switch(msg) {
226 case kNavCBPopupMenuSelect: {
227 qt_mac_nav_filter_type *t = (qt_mac_nav_filter_type *)myd;
228 NavMenuItemSpec *s = (NavMenuItemSpec*)p->eventData.eventDataParms.param;
229 t->index = s->menuType;
230#ifdef DEBUG_FILEDIALOG_FILTERS
231 qDebug("Q3FileDialog:%d - Selected a filter: %ld", __LINE__, s->menuType);
232#endif
233 break; }
234 case kNavCBStart:
235 g_nav_blocking=true;
236 break;
237 case kNavCBUserAction:
238 g_nav_blocking=false;
239 break;
240 }
241}
242static const NavEventUPP make_navProcUPP()
243{
244 if(mac_navProcUPP)
245 return mac_navProcUPP;
246 qAddPostRoutine(cleanup_navProcUPP);
247 return mac_navProcUPP = NewNavEventUPP(qt_mac_filedialog_event_proc);
248}
249
250
251extern OSErr qt_mac_create_fsref(const QString &, FSRef *); //qglobal.cpp
252
253QStringList Q3FileDialog::macGetOpenFileNames(const QString &filter, QString *pwd,
254 QWidget *parent, const char* /*name*/,
255 const QString& caption, QString *selectedFilter,
256 bool multi, bool directory)
257{
258 OSErr err;
259 QStringList retstrl;
260
261 NavDialogCreationOptions options;
262 NavGetDefaultDialogCreationOptions(&options);
263 options.modality = kWindowModalityAppModal;
264 options.optionFlags |= kNavDontConfirmReplacement | kNavSupportPackages;
265 if (!multi)
266 options.optionFlags &= ~kNavAllowMultipleFiles;
267 if(!caption.isEmpty())
268 options.windowTitle = CFStringCreateWithCharacters(NULL, (UniChar *)caption.unicode(),
269 caption.length());
270
271 static const int w = 450, h = 350;
272 options.location.h = options.location.v = -1;
273 if(parent && parent->isVisible()) {
274 Qt::WindowType wt = parent->window()->windowType();
275 if (wt != Qt::Desktop && wt != Qt::Sheet && wt != Qt::Drawer) {
276 options.modality = kWindowModalityWindowModal;
277 options.parentWindow = qt_mac_window_for(parent);
278 } else {
279 parent = parent->window();
280 QString s = parent->windowTitle();
281 options.clientName = CFStringCreateWithCharacters(NULL, (UniChar *)s.unicode(), s.length());
282 options.location.h = (parent->x() + (parent->width() / 2)) - (w / 2);
283 options.location.v = (parent->y() + (parent->height() / 2)) - (h / 2);
284
285 QRect r = QApplication::desktop()->screenGeometry(
286 QApplication::desktop()->screenNumber(parent));
287 if(options.location.h + w > r.right())
288 options.location.h -= (options.location.h + w) - r.right() + 10;
289 if(options.location.v + h > r.bottom())
290 options.location.v -= (options.location.v + h) - r.bottom() + 10;
291 }
292 } else if(QWidget *p = qApp->mainWidget()) {
293 static int last_screen = -1;
294 int scr = QApplication::desktop()->screenNumber(p);
295 if(last_screen != scr) {
296 QRect r = QApplication::desktop()->screenGeometry(scr);
297 options.location.h = (r.x() + (r.width() / 2)) - (w / 2);
298 options.location.v = (r.y() + (r.height() / 2)) - (h / 2);
299 }
300 }
301
302 QList<qt_mac_filter_name*> filts = makeFiltersList(filter);
303 qt_mac_nav_filter_type t;
304 t.index = 0;
305 t.filts = &filts;
306 if(filts.count() > 1) {
307 int i = 0;
308 CFStringRef *arr = (CFStringRef *)malloc(sizeof(CFStringRef) * filts.count());
309 for (QList<qt_mac_filter_name*>::Iterator it = filts.begin(); it != filts.end(); ++it) {
310 QString rg = (*it)->description;
311 arr[i++] = CFStringCreateWithCharacters(NULL, (UniChar *)rg.unicode(), rg.length());
312 }
313 options.popupExtension = CFArrayCreate(NULL, (const void **)arr, filts.count(), NULL);
314 }
315
316 NavDialogRef dlg;
317 if(directory) {
318 if(NavCreateChooseFolderDialog(&options, make_navProcUPP(), NULL, NULL, &dlg)) {
319 qDebug("Shouldn't happen %s:%d", __FILE__, __LINE__);
320 return retstrl;
321 }
322 } else {
323 if(NavCreateGetFileDialog(&options, NULL, make_navProcUPP(), NULL,
324 make_navFilterUPP(), (void *) (filts.isEmpty() ? NULL : &t),
325 &dlg)) {
326 qDebug("Shouldn't happen %s:%d", __FILE__, __LINE__);
327 return retstrl;
328 }
329 }
330 if(pwd && !pwd->isEmpty()) {
331 FSRef fsref;
332 if(qt_mac_create_fsref(*pwd, &fsref) == noErr) {
333 AEDesc desc;
334 if(AECreateDesc(typeFSRef, &fsref, sizeof(FSRef), &desc) == noErr)
335 NavCustomControl(dlg, kNavCtlSetLocation, (void*)&desc);
336 }
337 }
338
339 NavDialogRun(dlg);
340 if (selectedFilter) {
341 NavMenuItemSpec navSpec;
342 bzero(&navSpec, sizeof(NavMenuItemSpec));
343 qt_mac_filter_name *sel_filt_name = makeFiltersList(*selectedFilter).at(0);
344 for (int i = 0; i < filts.count(); ++i) {
345 const qt_mac_filter_name *filter = filts.at(i);
346 if (sel_filt_name->description == filter->description
347 && sel_filt_name->regxp == filter->regxp
348 && sel_filt_name->filter == filter->filter) {
349 navSpec.menuType = i;
350 break;
351 }
352 }
353 NavCustomControl(dlg, kNavCtlSelectCustomType, &navSpec);
354 }
355 if(options.modality == kWindowModalityWindowModal) { //simulate modality
356 QWidget modal_widg(parent, __FILE__ "__modal_dlg",
357 Qt::WType_TopLevel | Qt::WStyle_Customize | Qt::WStyle_DialogBorder);
358 modal_widg.createWinId();
359 QApplicationPrivate::enterModal(&modal_widg);
360 while(g_nav_blocking)
361 qApp->processEvents(QEventLoop::WaitForMoreEvents);
362 QApplicationPrivate::leaveModal(&modal_widg);
363 }
364
365 if(!(NavDialogGetUserAction(dlg) &
366 (kNavUserActionOpen | kNavUserActionChoose | kNavUserActionNewFolder))) {
367 NavDialogDispose(dlg);
368 return retstrl;
369 }
370 NavReplyRecord ret;
371 NavDialogGetReply(dlg, &ret);
372 NavDialogDispose(dlg);
373
374 long count;
375 err = AECountItems(&(ret.selection), &count);
376 if(!ret.validRecord || err != noErr || !count) {
377 NavDisposeReply(&ret);
378 return retstrl;
379 }
380
381 for(long index = 1; index <= count; index++) {
382 FSRef ref;
383 err = AEGetNthPtr(&(ret.selection), index, typeFSRef, 0, 0, &ref, sizeof(ref), 0);
384 if(err != noErr)
385 break;
386
387 if(!str_buffer) {
388 qAddPostRoutine(cleanup_str_buffer);
389 str_buffer = (UInt8 *)malloc(1024);
390 }
391 FSRefMakePath(&ref, str_buffer, 1024);
392 retstrl.append(QString::fromUtf8((const char *)str_buffer));
393 }
394 NavDisposeReply(&ret);
395 if(selectedFilter)
396 *selectedFilter = filts.at(t.index)->filter;
397 while (!filts.isEmpty())
398 delete filts.takeFirst();
399 return retstrl;
400}
401
402// Copious copy and paste from qfiledialog.cpp. Fix in 4.0.
403static QString encodeFileName(const QString &fName)
404{
405 QString newStr;
406 QByteArray cName = fName.utf8();
407 const QByteArray sChars("<>#@\"&%$:,;?={}|^~[]\'`\\*");
408
409 int len = cName.length();
410 if (!len)
411 return QString();
412 for (int i = 0; i < len ;++i) {
413 uchar inCh = (uchar)cName[i];
414 if (inCh >= 128 || sChars.contains(inCh))
415 {
416 newStr += QLatin1Char('%');
417 ushort c = inCh / 16;
418 c += c > 9 ? 'A' - 10 : '0';
419 newStr += QLatin1Char((char)c);
420 c = inCh % 16;
421 c += c > 9 ? 'A' - 10 : '0';
422 newStr += QLatin1Char((char)c);
423 } else {
424 newStr += QLatin1Char((char)inCh);
425 }
426 }
427 return newStr;
428}
429
430QString Q3FileDialog::macGetSaveFileName(const QString &start, const QString &filter,
431 QString *, QWidget *parent, const char* /*name*/,
432 const QString& caption, QString *selectedFilter)
433{
434 OSErr err;
435 QString retstr;
436 NavDialogCreationOptions options;
437 NavGetDefaultDialogCreationOptions(&options);
438 static const int w = 450, h = 350;
439 options.optionFlags |= kNavDontConfirmReplacement;
440 options.modality = kWindowModalityAppModal;
441 options.location.h = options.location.v = -1;
442 QString workingDir;
443 QString initialSelection;
444 if (!start.isEmpty()) {
445 Q3UrlOperator u(encodeFileName(start));
446 if (u.isLocalFile() && QFileInfo(u.path()).isDir()) {
447 workingDir = start;
448 } else {
449 if (u.isLocalFile()) {
450 QFileInfo fi(u.dirPath());
451 if (fi.exists()) {
452 workingDir = u.dirPath();
453 initialSelection = u.fileName();
454 }
455 } else {
456 workingDir = u.toString();
457 }
458 }
459 if (!initialSelection.isEmpty())
460 options.saveFileName = CFStringCreateWithCharacters(0,
461 (UniChar *)initialSelection.unicode(),
462 initialSelection.length());
463 }
464 if(!caption.isEmpty())
465 options.windowTitle = CFStringCreateWithCharacters(NULL, (UniChar *)caption.unicode(),
466 caption.length());
467 if(parent && parent->isVisible()) {
468 Qt::WindowType wt = parent->window()->windowType();
469 if (wt != Qt::Desktop && wt != Qt::Sheet && wt != Qt::Drawer) {
470 options.modality = kWindowModalityWindowModal;
471 options.parentWindow = qt_mac_window_for(parent);
472 } else {
473 parent = parent->window();
474 QString s = parent->windowTitle();
475 options.clientName = CFStringCreateWithCharacters(NULL, (UniChar *)s.unicode(), s.length());
476 options.location.h = (parent->x() + (parent->width() / 2)) - (w / 2);
477 options.location.v = (parent->y() + (parent->height() / 2)) - (h / 2);
478
479 QRect r = QApplication::desktop()->screenGeometry(
480 QApplication::desktop()->screenNumber(parent));
481 if(options.location.h + w > r.right())
482 options.location.h -= (options.location.h + w) - r.right() + 10;
483 if(options.location.v + h > r.bottom())
484 options.location.v -= (options.location.v + h) - r.bottom() + 10;
485 }
486 } else if(QWidget *p = qApp->mainWidget()) {
487 static int last_screen = -1;
488 int scr = QApplication::desktop()->screenNumber(p);
489 if(last_screen != scr) {
490 QRect r = QApplication::desktop()->screenGeometry(scr);
491 options.location.h = (r.x() + (r.width() / 2)) - (w / 2);
492 options.location.v = (r.y() + (r.height() / 2)) - (h / 2);
493 }
494 }
495
496 QList<qt_mac_filter_name*> filts = makeFiltersList(filter);
497 qt_mac_nav_filter_type t;
498 t.index = 0;
499 t.filts = &filts;
500 if(filts.count() > 1) {
501 int i = 0;
502 CFStringRef *arr = (CFStringRef *)malloc(sizeof(CFStringRef) * filts.count());
503 for (QList<qt_mac_filter_name*>::Iterator it = filts.begin(); it != filts.end(); ++it) {
504 QString rg = (*it)->description;
505 arr[i++] = CFStringCreateWithCharacters(NULL, (UniChar *)rg.unicode(), rg.length());
506 }
507 options.popupExtension = CFArrayCreate(NULL, (const void **)arr, filts.count(), NULL);
508 }
509
510 NavDialogRef dlg;
511 if(NavCreatePutFileDialog(&options, 'cute', kNavGenericSignature, make_navProcUPP(),
512 (void *) (filts.isEmpty() ? NULL : &t), &dlg)) {
513 qDebug("Shouldn't happen %s:%d", __FILE__, __LINE__);
514 return retstr;
515 }
516 if (!workingDir.isEmpty()) {
517 FSRef fsref;
518 if (qt_mac_create_fsref(workingDir, &fsref) == noErr) {
519 AEDesc desc;
520 if (AECreateDesc(typeFSRef, &fsref, sizeof(FSRef), &desc) == noErr)
521 NavCustomControl(dlg, kNavCtlSetLocation, (void*)&desc);
522 }
523 }
524 NavDialogRun(dlg);
525 if (selectedFilter) {
526 NavMenuItemSpec navSpec;
527 bzero(&navSpec, sizeof(NavMenuItemSpec));
528 qt_mac_filter_name *sel_filt_name = makeFiltersList(*selectedFilter).at(0);
529 for (int i = 0; i < filts.count(); ++i) {
530 const qt_mac_filter_name *filter = filts.at(i);
531 if (sel_filt_name->description == filter->description
532 && sel_filt_name->regxp == filter->regxp
533 && sel_filt_name->filter == filter->filter) {
534 navSpec.menuType = i;
535 break;
536 }
537 }
538 NavCustomControl(dlg, kNavCtlSelectCustomType, &navSpec);
539 }
540 if(options.modality == kWindowModalityWindowModal) { //simulate modality
541 QWidget modal_widg(parent, __FILE__ "__modal_dlg",
542 Qt::WType_TopLevel | Qt::WStyle_Customize | Qt::WStyle_DialogBorder);
543 modal_widg.createWinId();
544 QApplicationPrivate::enterModal(&modal_widg);
545 while(g_nav_blocking)
546 qApp->processEvents(QEventLoop::WaitForMoreEvents);
547 QApplicationPrivate::leaveModal(&modal_widg);
548 }
549
550 if(NavDialogGetUserAction(dlg) != kNavUserActionSaveAs) {
551 NavDialogDispose(dlg);
552 return retstr;
553 }
554 NavReplyRecord ret;
555 NavDialogGetReply(dlg, &ret);
556 NavDialogDispose(dlg);
557
558 long count;
559 err = AECountItems(&(ret.selection), &count);
560 if(!ret.validRecord || err != noErr || !count) {
561 NavDisposeReply(&ret);
562 return retstr;
563 }
564
565 AEKeyword keyword;
566 DescType type;
567 Size size;
568 FSRef ref;
569 err = AEGetNthPtr(&(ret.selection), 1, typeFSRef, &keyword,
570 &type, &ref, sizeof(ref), &size);
571 if(err == noErr) {
572 if(!str_buffer) {
573 qAddPostRoutine(cleanup_str_buffer);
574 str_buffer = (UInt8 *)malloc(1024);
575 }
576 FSRefMakePath(&ref, str_buffer, 1024);
577 retstr = QString::fromUtf8((const char *)str_buffer);
578 //now filename
579 CFStringGetCString(ret.saveFileName, (char *)str_buffer, 1024, kCFStringEncodingUTF8);
580 retstr += QLatin1Char('/') + QString::fromUtf8((const char *)str_buffer);
581 }
582 NavDisposeReply(&ret);
583 if(selectedFilter)
584 *selectedFilter = filts.at(t.index)->filter;
585 while (!filts.isEmpty())
586 delete filts.takeFirst();
587 return retstr;
588}
589
590#endif // QT_MAC_USE_COCOA
591
592QT_END_NAMESPACE
593
594#endif
Note: See TracBrowser for help on using the repository browser.