VirtualBox

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

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

FE/Qt4: Better solution for VBoxGlobal::iconSet[Ex] which magically brought action icons back.

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