VirtualBox

source: vbox/trunk/src/VBox/Frontends/VirtualBox4/ui/VBoxVMLogViewer.ui.h@ 7763

Last change on this file since 7763 was 7763, checked in by vboxsync, 17 years ago

FE/Qt4: Ported mostly of the VBoxGlobal related function to Qt4.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Revision Author Id
File size: 22.7 KB
Line 
1//Added by qt3to4:
2#include <Q3HBoxLayout>
3#include <QKeyEvent>
4#include <QLabel>
5#include <QPixmap>
6#include <q3mimefactory.h>
7#include <QHideEvent>
8#include <QResizeEvent>
9#include <QEvent>
10#include <Q3VBoxLayout>
11#include <QShowEvent>
12#include <QToolTip>
13/**
14 *
15 * VBox frontends: Qt GUI ("VirtualBox"):
16 * "Virtual Log Viewer" dialog UI include (Qt Designer)
17 */
18
19/*
20 * Copyright (C) 2006 innotek GmbH
21 *
22 * This file is part of VirtualBox Open Source Edition (OSE), as
23 * available from http://www.virtualbox.org. This file is free software;
24 * you can redistribute it and/or modify it under the terms of the GNU
25 * General Public License (GPL) as published by the Free Software
26 * Foundation, in version 2 as it comes in the "COPYING" file of the
27 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
28 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
29 */
30
31/****************************************************************************
32** ui.h extension file, included from the uic-generated form implementation.
33**
34** If you wish to add, delete or rename functions or slots use
35** Qt Designer which will update this file, preserving your code. Create an
36** init() function in place of a constructor, and a destroy() function in
37** place of a destructor.
38*****************************************************************************/
39
40
41class VBoxLogSearchPanel : public QWidget
42{
43 Q_OBJECT
44
45public:
46
47 VBoxLogSearchPanel (QWidget *aParent,
48 VBoxVMLogViewer *aViewer,
49 const char *aName)
50 : QWidget (aParent, aName)
51 , mViewer (aViewer)
52 , mButtonClose (0)
53 , mSearchName (0), mSearchString (0)
54 , mButtonPrev (0), mButtonNext (0)
55 , mCaseSensitive (0)
56 , mWarningSpacer (0), mWarningIcon (0), mWarningString (0)
57 {
58 mButtonClose = new QToolButton (this);
59 mButtonClose->setAutoRaise (true);
60 mButtonClose->setFocusPolicy (Qt::TabFocus);
61 mButtonClose->setAccel (QKeySequence (Qt::Key_Escape));
62 connect (mButtonClose, SIGNAL (clicked()), this, SLOT (hide()));
63 mButtonClose->setIconSet (VBoxGlobal::iconSet (":/delete_16px.png",
64 ":/delete_dis_16px.png"));
65
66 mSearchName = new QLabel (this);
67 mSearchString = new QLineEdit (this);
68 mSearchString->setSizePolicy (QSizePolicy::Preferred,
69 QSizePolicy::Fixed);
70 connect (mSearchString, SIGNAL (textChanged (const QString &)),
71 this, SLOT (findCurrent (const QString &)));
72
73 mButtonNext = new QToolButton (this);
74 mButtonNext->setEnabled (false);
75 mButtonNext->setAutoRaise (true);
76 mButtonNext->setFocusPolicy (Qt::TabFocus);
77 mButtonNext->setUsesTextLabel (true);
78 mButtonNext->setTextPosition (QToolButton::BesideIcon);
79 connect (mButtonNext, SIGNAL (clicked()), this, SLOT (findNext()));
80 mButtonNext->setIconSet (VBoxGlobal::iconSet (":/list_movedown_16px.png",
81 ":/list_movedown_disabled_16px.png"));
82
83 mButtonPrev = new QToolButton (this);
84 mButtonPrev->setEnabled (false);
85 mButtonPrev->setAutoRaise (true);
86 mButtonPrev->setFocusPolicy (Qt::TabFocus);
87 mButtonPrev->setUsesTextLabel (true);
88 mButtonPrev->setTextPosition (QToolButton::BesideIcon);
89 connect (mButtonPrev, SIGNAL (clicked()), this, SLOT (findBack()));
90 mButtonPrev->setIconSet (VBoxGlobal::iconSet (":/list_moveup_16px.png",
91 ":/list_moveup_disabled_16px.png"));
92
93 mCaseSensitive = new QCheckBox (this);
94
95 mWarningSpacer = new QSpacerItem (0, 0, QSizePolicy::Fixed,
96 QSizePolicy::Minimum);
97 mWarningIcon = new QLabel (this);
98 mWarningIcon->hide();
99 QImage img = QMessageBox::standardIcon (QMessageBox::Warning).
100 convertToImage();
101 if (!img.isNull())
102 {
103 img = img.smoothScale (16, 16);
104 QPixmap pixmap;
105 pixmap.convertFromImage (img);
106 mWarningIcon->setPixmap (pixmap);
107 }
108 mWarningString = new QLabel (this);
109 mWarningString->hide();
110
111 QSpacerItem *spacer = new QSpacerItem (0, 0, QSizePolicy::Expanding,
112 QSizePolicy::Minimum);
113
114 Q3HBoxLayout *mainLayout = new Q3HBoxLayout (this, 5, 5);
115 mainLayout->addWidget (mButtonClose);
116 mainLayout->addWidget (mSearchName);
117 mainLayout->addWidget (mSearchString);
118 mainLayout->addWidget (mButtonNext);
119 mainLayout->addWidget (mButtonPrev);
120 mainLayout->addWidget (mCaseSensitive);
121 mainLayout->addItem (mWarningSpacer);
122 mainLayout->addWidget (mWarningIcon);
123 mainLayout->addWidget (mWarningString);
124 mainLayout->addItem (spacer);
125
126 setFocusProxy (mCaseSensitive);
127 topLevelWidget()->installEventFilter (this);
128
129 languageChange();
130 }
131
132 void languageChange()
133 {
134 QKeySequence accel;
135
136 QToolTip::add (mButtonClose, tr ("Close the search panel"));
137
138 mSearchName->setText (tr ("Find "));
139 QToolTip::add (mSearchString, tr ("Enter a search string here"));
140
141 VBoxGlobal::setTextLabel (mButtonPrev, tr ("&Previous"));
142 QToolTip::add (mButtonPrev,
143 tr ("Search for the previous occurrence of the string"));
144
145 VBoxGlobal::setTextLabel (mButtonNext, tr ("&Next"));
146 QToolTip::add (mButtonNext,
147 tr ("Search for the next occurrence of the string"));
148
149 mCaseSensitive->setText (tr ("C&ase Sensitive"));
150 QToolTip::add (mCaseSensitive,
151 tr ("Perform case sensitive search (when checked)"));
152
153 mWarningString->setText (tr ("String not found"));
154 }
155
156private slots:
157
158 void findNext()
159 {
160 search (true);
161 }
162
163 void findBack()
164 {
165 search (false);
166 }
167
168 void findCurrent (const QString &aSearchString)
169 {
170 mButtonNext->setEnabled (aSearchString.length());
171 mButtonPrev->setEnabled (aSearchString.length());
172 toggleWarning (!aSearchString.length());
173 if (aSearchString.length())
174 search (true, true);
175 else
176 mViewer->currentLogPage()->removeSelection();
177 }
178
179private:
180
181 void search (bool aForward, bool aStartCurrent = false)
182 {
183 Q3TextBrowser *browser = mViewer->currentLogPage();
184 if (!browser) return;
185
186 int startPrg = 0, endPrg = 0;
187 int startInd = 0, endInd = 0;
188 if (browser->hasSelectedText())
189 browser->getSelection (&startPrg, &startInd, &endPrg, &endInd);
190
191 bool found = false;
192 int increment = aForward ? 1 : -1;
193 int border = aForward ? browser->paragraphs() : -1;
194 int startFrom = aStartCurrent ? startInd : startInd + increment;
195 int paragraph = startFrom < 0 ? startPrg + increment : startPrg;
196 for (; paragraph != border; paragraph += increment)
197 {
198 QString text = browser->text (paragraph);
199 int res = aForward ?
200 text.find (mSearchString->text(), startFrom,
201 mCaseSensitive->isChecked()) :
202 text.findRev (mSearchString->text(), startFrom,
203 mCaseSensitive->isChecked());
204 if (res != -1)
205 {
206 found = true;
207 browser->setSelection (paragraph, res, paragraph,
208 res + mSearchString->text().length());
209 /* ensures the selected word visible */
210 int curPrg = 0, curInd = 0;
211 browser->getCursorPosition (&curPrg, &curInd);
212 QRect rect = browser->paragraphRect (curPrg);
213 QString string = browser->text (curPrg);
214 string.truncate (curInd);
215 int x = rect.x() + browser->fontMetrics().width (string);
216 int y = rect.y() + browser->pointSize() / 2;
217 browser->setContentsPos (0, browser->contentsY());
218 browser->ensureVisible (x, y, 40, 40);
219 break;
220 }
221 startFrom = aForward ? 0 : -1;
222 }
223
224 toggleWarning (found);
225 if (!found)
226 browser->setSelection (startPrg, startInd, endPrg, endInd);
227 }
228
229 bool eventFilter (QObject *aObject, QEvent *aEvent)
230 {
231 switch (aEvent->type())
232 {
233 case QEvent::KeyPress:
234 {
235 QKeyEvent *e = static_cast<QKeyEvent*> (aEvent);
236
237 /* handle the Enter keypress for mSearchString
238 * widget as a search next string action */
239 if (aObject == mSearchString &&
240 (e->state() == 0 || e->state() & Qt::Keypad) &&
241 (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return))
242 {
243 findNext();
244 return true;
245 }
246 /* handle other search next/previous shortcuts */
247 else if (e->key() == Qt::Key_F3)
248 {
249 if (e->state() == 0)
250 findNext();
251 else if (e->state() == Qt::ShiftButton)
252 findBack();
253 return true;
254 }
255 /* handle ctrl-f key combination as a shortcut to
256 * move to the search field */
257 else if (e->state() == Qt::ControlButton && e->key() == Qt::Key_F)
258 {
259 if (mViewer->currentLogPage())
260 {
261 if (isHidden()) show();
262 mSearchString->setFocus();
263 return true;
264 }
265 }
266 /* handle alpha-numeric keys to implement the
267 * "find as you type" feature */
268 else if ((e->state() & ~Qt::ShiftButton) == 0 &&
269 e->key() >= Qt::Key_Exclam &&
270 e->key() <= Qt::Key_AsciiTilde)
271 {
272 if (mViewer->currentLogPage())
273 {
274 if (isHidden()) show();
275 mSearchString->setFocus();
276 mSearchString->insert (e->text());
277 return true;
278 }
279 }
280
281 break;
282 }
283 default:
284 break;
285 }
286 return false;
287 }
288
289 void showEvent (QShowEvent *aEvent)
290 {
291 QWidget::showEvent (aEvent);
292 mSearchString->setFocus();
293 mSearchString->selectAll();
294 }
295
296 void hideEvent (QHideEvent *aEvent)
297 {
298#warning port me
299// if (focusData()->focusWidget()->parent() == this)
300// focusNextPrevChild (true);
301 QWidget::hideEvent (aEvent);
302 }
303
304 void toggleWarning (bool aHide)
305 {
306 mWarningSpacer->changeSize (aHide ? 0 : 16, 0, QSizePolicy::Fixed,
307 QSizePolicy::Minimum);
308 mWarningIcon->setHidden (aHide);
309 mWarningString->setHidden (aHide);
310 }
311
312 VBoxVMLogViewer *mViewer;
313 QToolButton *mButtonClose;
314 QLabel *mSearchName;
315 QLineEdit *mSearchString;
316 QToolButton *mButtonPrev;
317 QToolButton *mButtonNext;
318 QCheckBox *mCaseSensitive;
319 QSpacerItem *mWarningSpacer;
320 QLabel *mWarningIcon;
321 QLabel *mWarningString;
322};
323
324
325VBoxVMLogViewer::LogViewersMap VBoxVMLogViewer::mSelfArray = LogViewersMap();
326
327void VBoxVMLogViewer::createLogViewer (CMachine &aMachine)
328{
329 if (mSelfArray.find (aMachine.GetName()) == mSelfArray.end())
330 {
331 /* creating new log viewer if there is no one existing */
332 mSelfArray [aMachine.GetName()] = new VBoxVMLogViewer (0,
333 "VBoxVMLogViewer", Qt::WType_TopLevel | Qt::WDestructiveClose);
334 /* read new machine data for this log viewer */
335 mSelfArray [aMachine.GetName()]->setup (aMachine);
336 }
337
338 VBoxVMLogViewer *viewer = mSelfArray [aMachine.GetName()];
339 viewer->show();
340 viewer->setWindowState (viewer->windowState() & ~Qt::WindowMinimized);
341 viewer->setActiveWindow();
342}
343
344
345void VBoxVMLogViewer::init()
346{
347 /* prepare dialog to first run */
348 mFirstRun = true;
349
350 /* dialog initially is not polished */
351 mIsPolished = false;
352
353 /* search the default button */
354 mDefaultButton = searchDefaultButton();
355 topLevelWidget()->installEventFilter (this);
356
357 /* setup a dialog icon */
358 setIcon (QPixmap (":/show_logs_16px.png"));
359
360 /* statusbar initially disabled */
361 statusBar()->setHidden (true);
362
363 /* setup size grip */
364 mSizeGrip = new QSizeGrip (centralWidget(), "mSizeGrip");
365 mSizeGrip->resize (mSizeGrip->sizeHint());
366 mSizeGrip->stackUnder (mCloseButton);
367
368 /* logs list creation */
369 mLogList = new QTabWidget (mLogsFrame, "mLogList");
370 Q3VBoxLayout *logsFrameLayout = new Q3VBoxLayout (mLogsFrame);
371 logsFrameLayout->addWidget (mLogList);
372
373 /* search panel creation */
374 mSearchPanel = new VBoxLogSearchPanel (mLogsFrame, this,
375 "VBoxLogSearchPanel");
376 logsFrameLayout->addWidget (mSearchPanel);
377 mSearchPanel->hide();
378
379 /* fix the tab order to ensure the dialog keys are always the last */
380 setTabOrder (mSearchPanel->focusProxy(), mHelpButton);
381 setTabOrder (mHelpButton, mFindButton);
382 setTabOrder (mFindButton, mSaveButton);
383 setTabOrder (mSaveButton, mRefreshButton);
384 setTabOrder (mRefreshButton, mCloseButton);
385 setTabOrder (mCloseButton, mLogList);
386
387 /* make the [Save] button focused by default */
388 mSaveButton->setFocus();
389
390 /* applying language settings */
391 languageChangeImp();
392}
393
394
395void VBoxVMLogViewer::destroy()
396{
397 mSelfArray.erase (mMachine.GetName());
398}
399
400
401void VBoxVMLogViewer::setup (CMachine &aMachine)
402{
403 /* saving related machine */
404 mMachine = aMachine;
405
406 /* reading log files */
407 refresh();
408
409 /* loading language constants */
410 languageChangeImp();
411}
412
413
414const CMachine& VBoxVMLogViewer::machine()
415{
416 return mMachine;
417}
418
419
420void VBoxVMLogViewer::languageChangeImp()
421{
422 /* setup a dialog caption */
423 if (!mMachine.isNull())
424 setCaption (tr ("%1 - VirtualBox Log Viewer").arg (mMachine.GetName()));
425 /* translate a search panel */
426 if (mSearchPanel)
427 mSearchPanel->languageChange();
428}
429
430
431QPushButton* VBoxVMLogViewer::searchDefaultButton()
432{
433 /* this mechanism is used for searching the default dialog button
434 * and similar the same mechanism in Qt::QDialog inner source */
435 QPushButton *button = 0;
436 QObjectList list = queryList ("QPushButton");
437 foreach (QObject *obj, list)
438 {
439 button = qobject_cast<QPushButton*> (obj);
440 if (button->isDefault())
441 break;
442 }
443 return button;
444}
445
446
447bool VBoxVMLogViewer::eventFilter (QObject *aObject, QEvent *aEvent)
448{
449 switch (aEvent->type())
450 {
451 /* auto-default button focus-in processor used to move the "default"
452 * button property into the currently focused button */
453 case QEvent::FocusIn:
454 {
455 if (aObject->inherits ("QPushButton") &&
456 aObject->parent() == centralWidget())
457 {
458 ((QPushButton*)aObject)->setDefault (aObject != mDefaultButton);
459 if (mDefaultButton)
460 mDefaultButton->setDefault (aObject == mDefaultButton);
461 }
462 break;
463 }
464 /* auto-default button focus-out processor used to remove the "default"
465 * button property from the previously focused button */
466 case QEvent::FocusOut:
467 {
468 if (aObject->inherits ("QPushButton") &&
469 aObject->parent() == centralWidget())
470 {
471 if (mDefaultButton)
472 mDefaultButton->setDefault (aObject != mDefaultButton);
473 ((QPushButton*)aObject)->setDefault (aObject == mDefaultButton);
474 }
475 break;
476 }
477 default:
478 break;
479 }
480 return Q3MainWindow::eventFilter (aObject, aEvent);
481}
482
483
484bool VBoxVMLogViewer::event (QEvent *aEvent)
485{
486 bool result = Q3MainWindow::event (aEvent);
487 switch (aEvent->type())
488 {
489 case QEvent::LanguageChange:
490 {
491 languageChangeImp();
492 break;
493 }
494 default:
495 break;
496 }
497 return result;
498}
499
500
501void VBoxVMLogViewer::keyPressEvent (QKeyEvent *aEvent)
502{
503 if (aEvent->state() == 0 ||
504 (aEvent->state() & Qt::KeypadModifier && aEvent->key() == Qt::Key_Enter))
505 {
506 switch (aEvent->key())
507 {
508 /* processing the return keypress for the auto-default button */
509 case Qt::Key_Enter:
510 case Qt::Key_Return:
511 {
512 QPushButton *currentDefault = searchDefaultButton();
513 if (currentDefault)
514 currentDefault->animateClick();
515 break;
516 }
517 /* processing the escape keypress as the close dialog action */
518 case Qt::Key_Escape:
519 {
520 mCloseButton->animateClick();
521 break;
522 }
523 }
524 }
525 else
526 aEvent->ignore();
527}
528
529
530void VBoxVMLogViewer::showEvent (QShowEvent *aEvent)
531{
532 Q3MainWindow::showEvent (aEvent);
533
534 /* one may think that QWidget::polish() is the right place to do things
535 * below, but apparently, by the time when QWidget::polish() is called,
536 * the widget style & layout are not fully done, at least the minimum
537 * size hint is not properly calculated. Since this is sometimes necessary,
538 * we provide our own "polish" implementation. */
539
540 if (mIsPolished)
541 return;
542
543 mIsPolished = true;
544
545 VBoxGlobal::centerWidget (this, parentWidget());
546}
547
548
549void VBoxVMLogViewer::resizeEvent (QResizeEvent*)
550{
551 /* adjust the size-grip location for the current resize event */
552 mSizeGrip->move (centralWidget()->rect().bottomRight() -
553 QPoint (mSizeGrip->rect().width() - 1,
554 mSizeGrip->rect().height() - 1));
555}
556
557
558void VBoxVMLogViewer::refresh()
559{
560 /* clearing old data if any */
561 mLogFilesList.clear();
562 mLogList->setEnabled (true);
563 while (mLogList->count())
564 {
565 QWidget *logPage = mLogList->page (0);
566 mLogList->removePage (logPage);
567 delete logPage;
568 }
569
570 bool isAnyLogPresent = false;
571
572 /* entering log files folder */
573 QString logFilesPath = mMachine.GetLogFolder();
574 QDir logFilesDir (logFilesPath);
575 if (logFilesDir.exists())
576 {
577 /* reading log files folder */
578 logFilesDir.setNameFilter ("*.log *.log.*");
579 QStringList logList = logFilesDir.entryList (QDir::Files);
580 if (!logList.empty()) isAnyLogPresent = true;
581 for (QStringList::Iterator it = logList.begin(); it != logList.end(); ++it)
582 loadLogFile (logFilesDir.filePath (*it));
583 }
584
585 /* create an empty log page if there are no logs at all */
586 if (!isAnyLogPresent)
587 {
588 Q3TextBrowser *dummyLog = createLogPage ("VBox.log");
589 dummyLog->setTextFormat (Qt::RichText);
590 dummyLog->setWordWrap (Q3TextEdit::WidgetWidth);
591 dummyLog->setText (tr ("<p>No log files found. Press the <b>Refresh</b> "
592 "button to rescan the log folder <nobr><b>%1</b></nobr>.</p>")
593 .arg (logFilesPath));
594 /* we don't want it to remain white */
595 dummyLog->setPaper (backgroundBrush());
596 }
597
598 /* restore previous tab-widget margin which was reseted when
599 * the tab widget's children was removed */
600 mLogList->setMargin (10);
601
602 /* show the first tab widget's page after the refresh */
603 mLogList->showPage (mLogList->page(0));
604
605 /* enable/disable save button & tab widget according log presence */
606 mFindButton->setEnabled (isAnyLogPresent);
607 mSaveButton->setEnabled (isAnyLogPresent);
608 mLogList->setEnabled (isAnyLogPresent);
609
610 if (mFirstRun)
611 {
612 /* resize the whole log-viewer to fit 80 symbols in text-browser for
613 * the first time started */
614 Q3TextBrowser *firstPage = static_cast <Q3TextBrowser *> (mLogList->page(0));
615 int fullWidth = firstPage->fontMetrics().width (QChar ('x')) * 80 +
616 firstPage->verticalScrollBar()->width() +
617 firstPage->frameWidth() * 2 +
618 5 + 4 /* left text margin + QTabWidget frame width */ +
619 mLogList->margin() * 2 +
620 centralWidget()->layout()->margin() * 2;
621 resize (fullWidth, height());
622 mFirstRun = false;
623 }
624}
625
626
627void VBoxVMLogViewer::loadLogFile (const QString &aFileName)
628{
629 /* prepare log file */
630 QFile logFile (aFileName);
631 if (!logFile.exists() || !logFile.open (QIODevice::ReadOnly))
632 return;
633
634 /* read log file and write it into the log page */
635 Q3TextBrowser *logViewer = createLogPage (QFileInfo (aFileName).fileName());
636 logViewer->setText (logFile.readAll());
637
638 mLogFilesList << aFileName;
639}
640
641
642Q3TextBrowser* VBoxVMLogViewer::createLogPage (const QString &aName)
643{
644 Q3TextBrowser *logViewer = new Q3TextBrowser();
645 logViewer->setTextFormat (Qt::PlainText);
646 QFont font = logViewer->currentFont();
647 font.setFamily ("Courier New,courier");
648 logViewer->setFont (font);
649 logViewer->setWordWrap (Q3TextEdit::NoWrap);
650 logViewer->setVScrollBarMode (Q3ScrollView::AlwaysOn);
651 mLogList->addTab (logViewer, aName);
652 return logViewer;
653}
654
655
656Q3TextBrowser* VBoxVMLogViewer::currentLogPage()
657{
658 return mLogList->isEnabled() ?
659 static_cast<Q3TextBrowser*> (mLogList->currentPage()) : 0;
660}
661
662
663void VBoxVMLogViewer::save()
664{
665 /* prepare "save as" dialog */
666 QFileInfo fileInfo (mLogFilesList [mLogList->currentPageIndex()]);
667 QDateTime dtInfo = fileInfo.lastModified();
668 QString dtString = dtInfo.toString ("yyyy-MM-dd-hh-mm-ss");
669 QString defaultFileName = QString ("%1-%2.log")
670 .arg (mMachine.GetName()).arg (dtString);
671 QString defaultFullName = QDir::convertSeparators (QDir::home().absPath() +
672 "/" + defaultFileName);
673
674 QString newFileName = Q3FileDialog::getSaveFileName (defaultFullName,
675 QString::null, this, "SaveLogAsDialog", tr ("Save VirtualBox Log As"));
676
677 /* save new log into the file */
678 if (!newFileName.isEmpty())
679 {
680 /* reread log data */
681 QFile oldFile (mLogFilesList [mLogList->currentPageIndex()]);
682 QFile newFile (newFileName);
683 if (!oldFile.open (QIODevice::ReadOnly) || !newFile.open (QIODevice::WriteOnly))
684 return;
685
686 /* save log data into the new file */
687 newFile.writeBlock (oldFile.readAll());
688 }
689}
690
691void VBoxVMLogViewer::search()
692{
693 mSearchPanel->isHidden() ? mSearchPanel->show() : mSearchPanel->hide();
694}
695
696#include "VBoxVMLogViewer.ui.moc"
697
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette