Kstars

fitsviewer.cpp
1 /*
2  SPDX-FileCopyrightText: 2004 Jasem Mutlaq <[email protected]>
3 
4  2006-03-03 Using CFITSIO, Porting to Qt4
5 
6  SPDX-License-Identifier: GPL-2.0-or-later
7 */
8 
9 #include "fitsviewer.h"
10 
11 #include "config-kstars.h"
12 
13 #include "fitsdata.h"
14 #include "fitsdebayer.h"
15 #include "fitstab.h"
16 #include "fitsview.h"
17 #include "kstars.h"
18 #include "ksutils.h"
19 #include "Options.h"
20 #ifdef HAVE_INDI
21 #include "indi/indilistener.h"
22 #endif
23 
24 #include <KActionCollection>
25 #include <KMessageBox>
26 #include <KToolBar>
27 #include <KNotifications/KStatusNotifierItem>
28 
29 #ifndef KSTARS_LITE
30 #include "fitshistogrameditor.h"
31 #endif
32 
33 #include <fits_debug.h>
34 
35 #define INITIAL_W 785
36 #define INITIAL_H 640
37 
38 QStringList FITSViewer::filterTypes =
39  QStringList() << I18N_NOOP("Auto Stretch") << I18N_NOOP("High Contrast") << I18N_NOOP("Equalize")
40  << I18N_NOOP("High Pass") << I18N_NOOP("Median") << I18N_NOOP("Gaussian blur")
41  << I18N_NOOP("Rotate Right") << I18N_NOOP("Rotate Left") << I18N_NOOP("Flip Horizontal")
42  << I18N_NOOP("Flip Vertical");
43 
45 {
46 #ifdef Q_OS_OSX
47  if (Options::independentWindowFITS())
49  else
50  {
52  connect(QApplication::instance(), SIGNAL(applicationStateChanged(Qt::ApplicationState)), this,
53  SLOT(changeAlwaysOnTop(Qt::ApplicationState)));
54  }
55 #endif
56 
57  fitsTabWidget = new QTabWidget(this);
58  undoGroup = new QUndoGroup(this);
59 
60  lastURL = QUrl(QDir::homePath());
61 
62  fitsTabWidget->setTabsClosable(true);
63 
64  setWindowIcon(QIcon::fromTheme("kstars_fitsviewer"));
65 
66  setCentralWidget(fitsTabWidget);
67 
68  connect(fitsTabWidget, &QTabWidget::currentChanged, this, &FITSViewer::tabFocusUpdated);
69  connect(fitsTabWidget, &QTabWidget::tabCloseRequested, this, &FITSViewer::closeTab);
70 
71  //These two connections will enable or disable the scope button if a scope is available or not.
72  //Of course this is also dependent on the presence of WCS data in the image.
73 
74 #ifdef HAVE_INDI
75  connect(INDIListener::Instance(), &INDIListener::newDevice, this, &FITSViewer::updateWCSFunctions);
76  connect(INDIListener::Instance(), &INDIListener::newDevice, this, &FITSViewer::updateWCSFunctions);
77 #endif
78 
79  led.setColor(Qt::green);
80 
81  fitsPosition.setAlignment(Qt::AlignCenter);
82  fitsPosition.setMinimumWidth(100);
83  fitsValue.setAlignment(Qt::AlignCenter);
84  fitsValue.setMinimumWidth(40);
85 
86  fitsWCS.setVisible(false);
87 
88  statusBar()->insertPermanentWidget(FITS_CLIP, &fitsClip);
89  statusBar()->insertPermanentWidget(FITS_HFR, &fitsHFR);
90  statusBar()->insertPermanentWidget(FITS_WCS, &fitsWCS);
91  statusBar()->insertPermanentWidget(FITS_VALUE, &fitsValue);
92  statusBar()->insertPermanentWidget(FITS_POSITION, &fitsPosition);
93  statusBar()->insertPermanentWidget(FITS_ZOOM, &fitsZoom);
94  statusBar()->insertPermanentWidget(FITS_RESOLUTION, &fitsResolution);
95  statusBar()->insertPermanentWidget(FITS_LED, &led);
96 
97  QAction *action = actionCollection()->addAction("rotate_right", this, SLOT(rotateCW()));
98 
99  action->setText(i18n("Rotate Right"));
100  action->setIcon(QIcon::fromTheme("object-rotate-right"));
101 
102  action = actionCollection()->addAction("rotate_left", this, SLOT(rotateCCW()));
103  action->setText(i18n("Rotate Left"));
104  action->setIcon(QIcon::fromTheme("object-rotate-left"));
105 
106  action = actionCollection()->addAction("flip_horizontal", this, SLOT(flipHorizontal()));
107  action->setText(i18n("Flip Horizontal"));
108  action->setIcon(
109  QIcon::fromTheme("object-flip-horizontal"));
110 
111  action = actionCollection()->addAction("flip_vertical", this, SLOT(flipVertical()));
112  action->setText(i18n("Flip Vertical"));
113  action->setIcon(QIcon::fromTheme("object-flip-vertical"));
114 
115  action = actionCollection()->addAction("image_histogram");
116  action->setText(i18n("Histogram"));
117  connect(action, SIGNAL(triggered(bool)), SLOT(histoFITS()));
119 
120  action->setIcon(QIcon(":/icons/histogram.png"));
121 
122  action = KStandardAction::open(this, SLOT(openFile()), actionCollection());
123  action->setIcon(QIcon::fromTheme("document-open"));
124 
125  saveFileAction = KStandardAction::save(this, SLOT(saveFile()), actionCollection());
126  saveFileAction->setIcon(QIcon::fromTheme("document-save"));
127 
128  saveFileAsAction = KStandardAction::saveAs(this, SLOT(saveFileAs()), actionCollection());
129  saveFileAsAction->setIcon(
130  QIcon::fromTheme("document-save_as"));
131 
132  action = actionCollection()->addAction("fits_header");
134  action->setIcon(QIcon::fromTheme("document-properties"));
135  action->setText(i18n("FITS Header"));
136  connect(action, SIGNAL(triggered(bool)), SLOT(headerFITS()));
137 
138  action = actionCollection()->addAction("fits_debayer");
140  action->setIcon(QIcon::fromTheme("view-preview"));
141  action->setText(i18n("Debayer..."));
142  connect(action, SIGNAL(triggered(bool)), SLOT(debayerFITS()));
143 
145  action->setIcon(QIcon::fromTheme("window-close"));
146 
147  action = KStandardAction::copy(this, SLOT(copyFITS()), actionCollection());
148  action->setIcon(QIcon::fromTheme("edit-copy"));
149 
150  action = KStandardAction::zoomIn(this, SLOT(ZoomIn()), actionCollection());
151  action->setIcon(QIcon::fromTheme("zoom-in"));
152 
153  action = KStandardAction::zoomOut(this, SLOT(ZoomOut()), actionCollection());
154  action->setIcon(QIcon::fromTheme("zoom-out"));
155 
156  action = KStandardAction::actualSize(this, SLOT(ZoomDefault()), actionCollection());
157  action->setIcon(QIcon::fromTheme("zoom-fit-best"));
158 
159  QAction *kundo = KStandardAction::undo(undoGroup, SLOT(undo()), actionCollection());
160  kundo->setIcon(QIcon::fromTheme("edit-undo"));
161 
162  QAction *kredo = KStandardAction::redo(undoGroup, SLOT(redo()), actionCollection());
163  kredo->setIcon(QIcon::fromTheme("edit-redo"));
164 
165  connect(undoGroup, SIGNAL(canUndoChanged(bool)), kundo, SLOT(setEnabled(bool)));
166  connect(undoGroup, SIGNAL(canRedoChanged(bool)), kredo, SLOT(setEnabled(bool)));
167 
168  action = actionCollection()->addAction("image_stats");
169  action->setIcon(QIcon::fromTheme("view-statistics"));
170  action->setText(i18n("Statistics"));
171  connect(action, SIGNAL(triggered(bool)), SLOT(statFITS()));
172 
173  action = actionCollection()->addAction("image_roi_stats");
174 
175  roiActionMenu = new KActionMenu(QIcon(":/icons/select_stat"), "Selection Statistics", action );
176  roiActionMenu->setText(i18n("&Selection Statistics"));
177  roiActionMenu->setDelayed(false);
178  roiActionMenu->addSeparator();
179  connect(roiActionMenu, &QAction::triggered, this, &FITSViewer::toggleSelectionMode);
180 
181  KToggleAction *ksa = actionCollection()->add<KToggleAction>("100x100");
182  ksa->setText("100x100");
183  ksa->setCheckable(false);
184  roiActionMenu->addAction(ksa);
185  ksa = actionCollection()->add<KToggleAction>("50x50");
186  ksa->setText("50x50");
187  ksa->setCheckable(false);
188  roiActionMenu->addAction(ksa);
189  ksa = actionCollection()->add<KToggleAction>("25x25");
190  ksa->setText("25x25");
191  ksa->setCheckable(false);
192  roiActionMenu->addAction(ksa);
193  ksa = actionCollection()->add<KToggleAction>("CustomRoi");
194  ksa->setText("Custom");
195  ksa->setCheckable(false);
196  roiActionMenu->addAction(ksa);
197 
198  action->setMenu(roiActionMenu->menu());
199  action->setIcon(QIcon(":/icons/select_stat"));
200  action->setCheckable(true);
201 
202  connect(roiActionMenu->menu()->actions().at(1), &QAction::triggered, this, [this] { ROIFixedSize(100); });
203  connect(roiActionMenu->menu()->actions().at(2), &QAction::triggered, this, [this] { ROIFixedSize(50); });
204  connect(roiActionMenu->menu()->actions().at(3), &QAction::triggered, this, [this] { ROIFixedSize(25); });
205  connect(roiActionMenu->menu()->actions().at(4), &QAction::triggered, this, [this] { customROIInputWindow();});
206  connect(action, &QAction::triggered, this, &FITSViewer::toggleSelectionMode);
207 
208  action = actionCollection()->addAction("view_crosshair");
209  action->setIcon(QIcon::fromTheme("crosshairs"));
210  action->setText(i18n("Show Cross Hairs"));
211  action->setCheckable(true);
212  connect(action, SIGNAL(triggered(bool)), SLOT(toggleCrossHair()));
213 
214  action = actionCollection()->addAction("view_clipping");
216  action->setIcon(QIcon::fromTheme("media-record"));
217  action->setText(i18n("Show Clipping"));
218  action->setCheckable(true);
219  connect(action, SIGNAL(triggered(bool)), SLOT(toggleClipping()));
220 
221  action = actionCollection()->addAction("view_pixel_grid");
222  action->setIcon(QIcon::fromTheme("map-flat"));
223  action->setText(i18n("Show Pixel Gridlines"));
224  action->setCheckable(true);
225  connect(action, SIGNAL(triggered(bool)), SLOT(togglePixelGrid()));
226 
227  action = actionCollection()->addAction("view_eq_grid");
228  action->setIcon(QIcon::fromTheme("kstars_grid"));
229  action->setText(i18n("Show Equatorial Gridlines"));
230  action->setCheckable(true);
231  action->setDisabled(true);
232  connect(action, SIGNAL(triggered(bool)), SLOT(toggleEQGrid()));
233 
234  action = actionCollection()->addAction("view_objects");
235  action->setIcon(QIcon::fromTheme("help-hint"));
236  action->setText(i18n("Show Objects in Image"));
237  action->setCheckable(true);
238  action->setDisabled(true);
239  connect(action, SIGNAL(triggered(bool)), SLOT(toggleObjects()));
240 
241  action = actionCollection()->addAction("center_telescope");
242  action->setIcon(QIcon(":/icons/center_telescope.svg"));
243  action->setText(i18n("Center Telescope\n*No Telescopes Detected*"));
244  action->setDisabled(true);
245  action->setCheckable(true);
246  connect(action, SIGNAL(triggered(bool)), SLOT(centerTelescope()));
247 
248  action = actionCollection()->addAction("view_zoom_fit");
249  action->setIcon(QIcon::fromTheme("zoom-fit-width"));
250  action->setText(i18n("Zoom To Fit"));
251  connect(action, SIGNAL(triggered(bool)), SLOT(ZoomToFit()));
252 
253 #ifdef HAVE_DATAVISUALIZATION
254  action = actionCollection()->addAction("toggle_3D_graph");
255  action->setIcon(QIcon::fromTheme("star_profile", QIcon(":/icons/star_profile.svg")));
256  action->setText(i18n("View 3D Graph"));
257  action->setCheckable(true);
258  connect(action, SIGNAL(triggered(bool)), SLOT(toggle3DGraph()));
259 #endif
260 
261  action = actionCollection()->addAction("mark_stars");
262  action->setText(i18n("Mark Stars"));
263  connect(action, SIGNAL(triggered(bool)), SLOT(toggleStars()));
264 
265  int filterCounter = 1;
266 
267  for (auto &filter : FITSViewer::filterTypes)
268  {
269  action = actionCollection()->addAction(QString("filter%1").arg(filterCounter));
270  action->setText(i18n(filter.toUtf8().constData()));
271  connect(action, &QAction::triggered, this, [this, filterCounter] { applyFilter(filterCounter);});
272  filterCounter++;
273  }
274 
276  /* Create GUI */
277  createGUI("fitsviewerui.rc");
278 
279  setWindowTitle(i18nc("@title:window", "KStars FITS Viewer"));
280 
281  /* initially resize in accord with KDE rules */
282  show();
283  resize(INITIAL_W, INITIAL_H);
284 }
285 
286 void FITSViewer::changeAlwaysOnTop(Qt::ApplicationState state)
287 {
288  if (isVisible())
289  {
290  if (state == Qt::ApplicationActive)
292  else
294  show();
295  }
296 }
297 
298 FITSViewer::~FITSViewer()
299 {
300  // if (KStars::Instance())
301  // {
302  // for (QPointer<FITSViewer> fv : KStars::Instance()->getFITSViewersList())
303  // {
304  // if (fv.data() == this)
305  // {
306  // KStars::Instance()->getFITSViewersList().removeOne(this);
307  // break;
308  // }
309  // }
310  // }
311 
312  fitsTabWidget->disconnect();
313 
314  qDeleteAll(fitsTabs);
315  fitsTabs.clear();
316 }
317 
318 void FITSViewer::closeEvent(QCloseEvent * /*event*/)
319 {
320  KStars *ks = KStars::Instance();
321 
322  if (ks)
323  {
324  QAction *a = KStars::Instance()->actionCollection()->action("show_fits_viewer");
326 
327  if (a && viewers.count() == 1)
328  {
329  a->setEnabled(false);
330  a->setChecked(false);
331  }
332  }
333 
334  //emit terminated();
335 }
336 
337 void FITSViewer::hideEvent(QHideEvent * /*event*/)
338 {
339  KStars *ks = KStars::Instance();
340 
341  if (ks)
342  {
343  QAction *a = KStars::Instance()->actionCollection()->action("show_fits_viewer");
344  if (a)
345  {
347 
348  if (viewers.count() <= 1)
349  a->setChecked(false);
350  }
351  }
352 }
353 
354 void FITSViewer::showEvent(QShowEvent * /*event*/)
355 {
356  QAction *a = KStars::Instance()->actionCollection()->action("show_fits_viewer");
357  if (a)
358  {
359  a->setEnabled(true);
360  a->setChecked(true);
361  }
362 }
363 
364 
365 namespace
366 {
367 QString HFRStatusString(const QSharedPointer<FITSData> &data)
368 {
369  const double hfrValue = data->getHFR();
370  if (hfrValue <= 0.0) return QString("");
371  if (data->getSkyBackground().starsDetected > 0)
372  return
373  i18np("HFR:%2 Ecc:%3 %1 star.", "HFR:%2 Ecc:%3 %1 stars.",
374  data->getSkyBackground().starsDetected,
375  QString::number(hfrValue, 'f', 2),
376  QString::number(data->getEccentricity(), 'f', 2));
377  else
378  return
379  i18np("HFR:%2, %1 star.", "HFR:%2, %1 stars.",
380  data->getDetectedStars(),
381  QString::number(hfrValue, 'f', 2));
382 }
383 
384 QString HFRClipString(FITSView* view)
385 {
386  if (view->isClippingShown())
387  return QString("Clip:%1").arg(view->getNumClipped());
388  return "";
389 }
390 } // namespace
391 
392 bool FITSViewer::addFITSCommon(FITSTab *tab, const QUrl &imageName,
393  FITSMode mode, const QString &previewText)
394 {
395  int tabIndex = fitsTabWidget->indexOf(tab);
396  if (tabIndex != -1)
397  return false;
398 
399  if (!imageName.isValid())
400  lastURL = QUrl(imageName.url(QUrl::RemoveFilename));
401 
403  tab->setPreviewText(previewText);
404 
405  // Connect tab signals
406  connect(tab, &FITSTab::newStatus, this, &FITSViewer::updateStatusBar);
407  connect(tab, &FITSTab::changeStatus, this, &FITSViewer::updateTabStatus);
408  connect(tab, &FITSTab::debayerToggled, this, &FITSViewer::setDebayerAction);
409  // Connect tab view signals
410  connect(tab->getView().get(), &FITSView::actionUpdated, this, &FITSViewer::updateAction);
411  connect(tab->getView().get(), &FITSView::wcsToggled, this, &FITSViewer::updateWCSFunctions);
412  connect(tab->getView().get(), &FITSView::starProfileWindowClosed, this, &FITSViewer::starProfileButtonOff);
413 
414  switch (mode)
415  {
416  case FITS_NORMAL:
417  fitsTabWidget->addTab(tab, previewText.isEmpty() ? imageName.fileName() : previewText);
418  break;
419 
420  case FITS_CALIBRATE:
421  fitsTabWidget->addTab(tab, i18n("Calibrate"));
422  break;
423 
424  case FITS_FOCUS:
425  fitsTabWidget->addTab(tab, i18n("Focus"));
426  break;
427 
428  case FITS_GUIDE:
429  fitsTabWidget->addTab(tab, i18n("Guide"));
430  break;
431 
432  case FITS_ALIGN:
433  fitsTabWidget->addTab(tab, i18n("Align"));
434  break;
435 
436  case FITS_UNKNOWN:
437  break;
438  }
439 
440  saveFileAction->setEnabled(true);
441  saveFileAsAction->setEnabled(true);
442 
443  undoGroup->addStack(tab->getUndoStack());
444 
445  fitsTabs.push_back(tab);
446 
447  fitsMap[fitsID] = tab;
448 
449  fitsTabWidget->setCurrentWidget(tab);
450 
451  actionCollection()->action("fits_debayer")->setEnabled(tab->getView()->imageData()->hasDebayer());
452 
453  tab->tabPositionUpdated();
454 
455  tab->setUID(fitsID);
456 
457  led.setColor(Qt::green);
458 
459  if (tab->shouldComputeHFR())
460  updateStatusBar(HFRStatusString(tab->getView()->imageData()), FITS_HFR);
461  else
462  updateStatusBar("", FITS_HFR);
463  updateStatusBar(i18n("Ready."), FITS_MESSAGE);
464 
465  updateStatusBar(HFRClipString(tab->getView().get()), FITS_CLIP);
466 
467  tab->getView()->setCursorMode(FITSView::dragCursor);
468 
470 
471  return true;
472 }
473 
474 void FITSViewer::loadFile(const QUrl &imageName, FITSMode mode, FITSScale filter, const QString &previewText)
475 {
476  led.setColor(Qt::yellow);
478 
479  FITSTab *tab = new FITSTab(this);
480 
481  connect(tab, &FITSTab::failed, this, [&](const QString & errorMessage)
482  {
484  led.setColor(Qt::red);
485  if (fitsTabs.size() == 0)
486  {
487  // Close FITS Viewer and let KStars know it is no longer needed in memory.
488  close();
489  }
490 
491  emit failed(errorMessage);
492  });
493 
494  connect(tab, &FITSTab::loaded, this, [ = ]()
495  {
496  if (addFITSCommon(tab, imageName, mode, previewText))
497  emit loaded(fitsID++);
498  });
499 
500  tab->loadFile(imageName, mode, filter);
501 }
502 
503 bool FITSViewer::loadData(const QSharedPointer<FITSData> &data, const QUrl &imageName, int *tab_uid, FITSMode mode,
504  FITSScale filter, const QString &previewText)
505 {
506  led.setColor(Qt::yellow);
508 
509  FITSTab *tab = new FITSTab(this);
510 
511  if (!tab->loadData(data, mode, filter))
512  {
513  auto errorMessage = tab->getView()->imageData()->getLastError();
515  led.setColor(Qt::red);
516  if (fitsTabs.size() == 0)
517  {
518  // Close FITS Viewer and let KStars know it is no longer needed in memory.
519  close();
520  }
521  emit failed(errorMessage);
522  return false;
523  }
524 
525  if (!addFITSCommon(tab, imageName, mode, previewText))
526  return false;
527 
528  *tab_uid = fitsID++;
529  return true;
530 }
531 
532 bool FITSViewer::removeFITS(int fitsUID)
533 {
534  FITSTab *tab = fitsMap.value(fitsUID);
535 
536  if (tab == nullptr)
537  {
538  qCWarning(KSTARS_FITS) << "Cannot find tab with UID " << fitsUID << " in the FITS Viewer";
539  return false;
540  }
541 
542  int index = fitsTabs.indexOf(tab);
543 
544  if (index >= 0)
545  {
546  closeTab(index);
547  return true;
548  }
549 
550  return false;
551 }
552 
553 void FITSViewer::updateFile(const QUrl &imageName, int fitsUID, FITSScale filter)
554 {
555  FITSTab *tab = fitsMap.value(fitsUID);
556 
557  if (tab == nullptr)
558  {
559  QString message = i18n("Cannot find tab with UID %1 in the FITS Viewer", fitsUID);
560  emit failed(message);
561  return;
562  }
563 
564  if (tab->isVisible())
565  led.setColor(Qt::yellow);
566 
567  // On tab load success
568  auto conn = std::make_shared<QMetaObject::Connection>();
569  *conn = connect(tab, &FITSTab::loaded, this, [ = ]()
570  {
571  if (updateFITSCommon(tab, imageName))
572  {
573  QObject::disconnect(*conn);
574  emit loaded(tab->getUID());
575  }
576  });
577 
578  tab->loadFile(imageName, tab->getView()->getMode(), filter);
579 }
580 
581 bool FITSViewer::updateFITSCommon(FITSTab *tab, const QUrl &imageName)
582 {
583  // On tab load success
584  int tabIndex = fitsTabWidget->indexOf(tab);
585  if (tabIndex == -1)
586  return false;
587 
588  if (tab->getView()->getMode() == FITS_NORMAL)
589  {
590  if ((imageName.path().startsWith(QLatin1String("/tmp")) ||
591  imageName.path().contains("/Temp")) &&
592  Options::singlePreviewFITS())
593  fitsTabWidget->setTabText(tabIndex,
594  tab->getPreviewText().isEmpty() ? i18n("Preview") : tab->getPreviewText());
595  else
596  fitsTabWidget->setTabText(tabIndex, imageName.fileName());
597  }
598 
599  tab->getUndoStack()->clear();
600 
601  if (tab->isVisible())
602  led.setColor(Qt::green);
603 
604  if (tab->shouldComputeHFR())
605  updateStatusBar(HFRStatusString(tab->getView()->imageData()), FITS_HFR);
606  else
607  updateStatusBar("", FITS_HFR);
608 
609  updateStatusBar(HFRClipString(tab->getView().get()), FITS_CLIP);
610 
611  return true;
612 }
613 
614 bool FITSViewer::updateData(const QSharedPointer<FITSData> &data, const QUrl &imageName, int fitsUID, int *tab_uid,
615  FITSScale filter, FITSMode mode)
616 {
617  FITSTab *tab = fitsMap.value(fitsUID);
618 
619  if (mode != FITS_UNKNOWN)
620  tab->getView()->updateMode(mode);
621 
622  if (tab == nullptr)
623  return false;
624 
625  if (tab->isVisible())
626  led.setColor(Qt::yellow);
627 
628  if (!tab->loadData(data, tab->getView()->getMode(), filter))
629  return false;
630 
631  if (!updateFITSCommon(tab, imageName))
632  return false;
633 
634  *tab_uid = tab->getUID();
635  return true;
636 }
637 
638 void FITSViewer::tabFocusUpdated(int currentIndex)
639 {
640  if (currentIndex < 0 || fitsTabs.empty())
641  return;
642 
643  fitsTabs[currentIndex]->tabPositionUpdated();
644 
645  auto view = fitsTabs[currentIndex]->getView();
646 
647  view->toggleStars(markStars);
648 
649  if (isVisible())
650  view->updateFrame();
651 
652  if (fitsTabs[currentIndex]->shouldComputeHFR())
653  updateStatusBar(HFRStatusString(view->imageData()), FITS_HFR);
654  else
655  updateStatusBar("", FITS_HFR);
656 
657  updateStatusBar(HFRClipString(fitsTabs[currentIndex]->getView().get()), FITS_CLIP);
658 
659  if (view->imageData()->hasDebayer())
660  {
661  actionCollection()->action("fits_debayer")->setEnabled(true);
662 
663  if (debayerDialog)
664  {
665  BayerParams param;
666  view->imageData()->getBayerParams(&param);
667  debayerDialog->setBayerParams(&param);
668  }
669  }
670  else
671  actionCollection()->action("fits_debayer")->setEnabled(false);
672 
673  updateStatusBar("", FITS_WCS);
674  connect(view.get(), &FITSView::starProfileWindowClosed, this, &FITSViewer::starProfileButtonOff);
675  QSharedPointer<FITSView> currentView;
676  if (getCurrentView(currentView))
677  {
678  updateButtonStatus("toggle_3D_graph", i18n("currentView 3D Graph"), currentView->isStarProfileShown());
679  updateButtonStatus("currentView_crosshair", i18n("Cross Hairs"), currentView->isCrosshairShown());
680  updateButtonStatus("currentView_clipping", i18n("Clipping"), currentView->isClippingShown());
681  updateButtonStatus("currentView_eq_grid", i18n("Equatorial Gridines"), currentView->isEQGridShown());
682  updateButtonStatus("currentView_objects", i18n("Objects in Image"), currentView->areObjectsShown());
683  updateButtonStatus("currentView_pixel_grid", i18n("Pixel Gridlines"), currentView->isPixelGridShown());
684  }
685 
686  updateScopeButton();
688 }
689 
690 void FITSViewer::starProfileButtonOff()
691 {
692  updateButtonStatus("toggle_3D_graph", i18n("View 3D Graph"), false);
693 }
694 
695 void FITSViewer::openFile()
696 {
697  QUrl fileURL = QFileDialog::getOpenFileUrl(KStars::Instance(), i18nc("@title:window", "Open Image"), lastURL,
698  "Images (*.fits *.fits.fz *.fit *.fts "
699  "*.jpg *.jpeg *.png *.gif *.bmp "
700  "*.cr2 *.cr3 *.crw *.nef *.raf *.dng *.arw *.orf)");
701 
702  if (fileURL.isEmpty())
703  return;
704 
705  lastURL = QUrl(fileURL.url(QUrl::RemoveFilename));
706  QString fpath = fileURL.toLocalFile();
707  QString cpath;
708 
709  // Make sure we don't have it open already, if yes, switch to it
710  for (auto tab : fitsTabs)
711  {
712  cpath = tab->getCurrentURL()->path();
713  if (fpath == cpath)
714  {
715  fitsTabWidget->setCurrentWidget(tab);
716  return;
717  }
718  }
719 
720  loadFile(fileURL, FITS_NORMAL, FITS_NONE, QString());
721 }
722 
723 void FITSViewer::saveFile()
724 {
725  fitsTabs[fitsTabWidget->currentIndex()]->saveFile();
726 }
727 
728 void FITSViewer::saveFileAs()
729 {
730  if (fitsTabs.empty())
731  return;
732 
733  if (fitsTabs[fitsTabWidget->currentIndex()]->saveFileAs() &&
734  fitsTabs[fitsTabWidget->currentIndex()]->getView()->getMode() == FITS_NORMAL)
735  fitsTabWidget->setTabText(fitsTabWidget->currentIndex(),
736  fitsTabs[fitsTabWidget->currentIndex()]->getCurrentURL()->fileName());
737 }
738 
739 void FITSViewer::copyFITS()
740 {
741  if (fitsTabs.empty())
742  return;
743 
744  fitsTabs[fitsTabWidget->currentIndex()]->copyFITS();
745 }
746 
747 void FITSViewer::histoFITS()
748 {
749  if (fitsTabs.empty())
750  return;
751 
752  fitsTabs[fitsTabWidget->currentIndex()]->histoFITS();
753 }
754 
755 void FITSViewer::statFITS()
756 {
757  if (fitsTabs.empty())
758  return;
759 
760  fitsTabs[fitsTabWidget->currentIndex()]->statFITS();
761 }
762 
763 void FITSViewer::rotateCW()
764 {
765  applyFilter(FITS_ROTATE_CW);
766 }
767 
768 void FITSViewer::rotateCCW()
769 {
770  applyFilter(FITS_ROTATE_CCW);
771 }
772 
773 void FITSViewer::flipHorizontal()
774 {
775  applyFilter(FITS_FLIP_H);
776 }
777 
778 void FITSViewer::flipVertical()
779 {
780  applyFilter(FITS_FLIP_V);
781 }
782 
783 void FITSViewer::headerFITS()
784 {
785  if (fitsTabs.empty())
786  return;
787 
788  fitsTabs[fitsTabWidget->currentIndex()]->headerFITS();
789 }
790 
791 void FITSViewer::debayerFITS()
792 {
793  if (debayerDialog == nullptr)
794  {
795  debayerDialog = new FITSDebayer(this);
796  }
797 
799  if (getCurrentView(view))
800  {
801  BayerParams param;
802  view->imageData()->getBayerParams(&param);
803  debayerDialog->setBayerParams(&param);
804  debayerDialog->show();
805  }
806 }
807 
808 void FITSViewer::updateStatusBar(const QString &msg, FITSBar id)
809 {
810  switch (id)
811  {
812  case FITS_POSITION:
813  fitsPosition.setText(msg);
814  break;
815  case FITS_RESOLUTION:
816  fitsResolution.setText(msg);
817  break;
818  case FITS_ZOOM:
819  fitsZoom.setText(msg);
820  break;
821  case FITS_WCS:
822  fitsWCS.setVisible(true);
823  fitsWCS.setText(msg);
824  break;
825  case FITS_VALUE:
826  fitsValue.setText(msg);
827  break;
828  case FITS_HFR:
829  fitsHFR.setText(msg);
830  break;
831  case FITS_CLIP:
832  fitsClip.setText(msg);
833  break;
834  case FITS_MESSAGE:
835  statusBar()->showMessage(msg);
836  break;
837 
838  default:
839  break;
840  }
841 }
842 
843 void FITSViewer::ZoomIn()
844 {
845  if (fitsTabs.empty())
846  return;
847 
848  fitsTabs[fitsTabWidget->currentIndex()]->ZoomIn();
849 }
850 
851 void FITSViewer::ZoomOut()
852 {
853  if (fitsTabs.empty())
854  return;
855 
856  fitsTabs[fitsTabWidget->currentIndex()]->ZoomOut();
857 }
858 
859 void FITSViewer::ZoomDefault()
860 {
861  if (fitsTabs.empty())
862  return;
863 
864  fitsTabs[fitsTabWidget->currentIndex()]->ZoomDefault();
865 }
866 
867 void FITSViewer::ZoomToFit()
868 {
869  if (fitsTabs.empty())
870  return;
871 
872  QSharedPointer<FITSView> currentView;
873  if (getCurrentView(currentView))
874  currentView->ZoomToFit();
875 }
876 
877 void FITSViewer::updateAction(const QString &name, bool enable)
878 {
879  QAction *toolAction = actionCollection()->action(name);
880 
881  if (toolAction != nullptr)
882  toolAction->setEnabled(enable);
883 }
884 
885 void FITSViewer::updateTabStatus(bool clean, const QUrl &imageURL)
886 {
887  if (fitsTabs.empty() || (fitsTabWidget->currentIndex() >= fitsTabs.size()))
888  return;
889 
890  if (fitsTabs[fitsTabWidget->currentIndex()]->getView()->getMode() != FITS_NORMAL)
891  return;
892 
893  //QString tabText = fitsImages[fitsTab->currentIndex()]->getCurrentURL()->fileName();
894 
895  QString tabText = imageURL.isEmpty() ? fitsTabWidget->tabText(fitsTabWidget->currentIndex()) : imageURL.fileName();
896 
897  fitsTabWidget->setTabText(fitsTabWidget->currentIndex(), clean ? tabText.remove('*') : tabText + '*');
898 }
899 
900 void FITSViewer::closeTab(int index)
901 {
902  if (fitsTabs.empty())
903  return;
904 
905  FITSTab *tab = fitsTabs[index];
906 
907  int UID = tab->getUID();
908 
909  fitsMap.remove(UID);
910  fitsTabs.removeOne(tab);
911  delete tab;
912 
913  if (fitsTabs.empty())
914  {
915  saveFileAction->setEnabled(false);
916  saveFileAsAction->setEnabled(false);
917  }
918 
919  emit closed(UID);
920 }
921 
922 /**
923  This is helper function to make it really easy to make the update the state of toggle buttons
924  that either show or hide information in the Current view. This method would get called both
925  when one of them gets pushed and also when tabs are switched.
926  */
927 
928 void FITSViewer::updateButtonStatus(const QString &action, const QString &item, bool showing)
929 {
931  if (a == nullptr)
932  return;
933 
934  if (showing)
935  {
936  a->setText(i18n("Hide %1", item));
937  a->setChecked(true);
938  }
939  else
940  {
941  a->setText(i18n("Show %1", item));
942  a->setChecked(false);
943  }
944 }
945 
946 /**
947 This is a method that either enables or disables the WCS based features in the Current View.
948  */
949 
951 {
952  QSharedPointer<FITSView> currentView;
953  if (!getCurrentView(currentView))
954  return;
955 
956  if (currentView->imageHasWCS())
957  {
958  actionCollection()->action("view_eq_grid")->setDisabled(false);
959  actionCollection()->action("view_eq_grid")->setText(i18n("Show Equatorial Gridlines"));
960  actionCollection()->action("view_objects")->setDisabled(false);
961  actionCollection()->action("view_objects")->setText(i18n("Show Objects in Image"));
962  if (currentView->isTelescopeActive())
963  {
964  actionCollection()->action("center_telescope")->setDisabled(false);
965  actionCollection()->action("center_telescope")->setText(i18n("Center Telescope\n*Ready*"));
966  }
967  else
968  {
969  actionCollection()->action("center_telescope")->setDisabled(true);
970  actionCollection()->action("center_telescope")->setText(i18n("Center Telescope\n*No Telescopes Detected*"));
971  }
972  }
973  else
974  {
975  actionCollection()->action("view_eq_grid")->setDisabled(true);
976  actionCollection()->action("view_eq_grid")->setText(i18n("Show Equatorial Gridlines\n*No WCS Info*"));
977  actionCollection()->action("center_telescope")->setDisabled(true);
978  actionCollection()->action("center_telescope")->setText(i18n("Center Telescope\n*No WCS Info*"));
979  actionCollection()->action("view_objects")->setDisabled(true);
980  actionCollection()->action("view_objects")->setText(i18n("Show Objects in Image\n*No WCS Info*"));
981  }
982 }
983 
984 void FITSViewer::updateScopeButton()
985 {
986  QSharedPointer<FITSView> currentView;
987  if (!getCurrentView(currentView))
988  return;
989 
990  if (currentView->getCursorMode() == FITSView::scopeCursor)
991  {
992  actionCollection()->action("center_telescope")->setChecked(true);
993  }
994  else
995  {
996  actionCollection()->action("center_telescope")->setChecked(false);
997  }
998 }
999 
1000 void FITSViewer::ROIFixedSize(int s)
1001 {
1002  if (fitsTabs.empty())
1003  return;
1004 
1005  QSharedPointer<FITSView> currentView;
1006  if (getCurrentView(currentView))
1007  {
1008  if(!currentView->isSelectionRectShown())
1009  {
1010  toggleSelectionMode();
1011  updateButtonStatus("image_roi_stats", i18n("Selection Rectangle"), currentView->isSelectionRectShown());
1012  }
1013  currentView->processRectangleFixed(s);
1014  }
1015 }
1016 
1017 void FITSViewer::customROIInputWindow()
1018 {
1019  if(fitsTabs.empty())
1020  return;
1021 
1022  QSharedPointer<FITSView> currentView;
1023  if (getCurrentView(currentView))
1024  {
1025  if(!currentView->isSelectionRectShown())
1026  return;
1027 
1028  int mh = currentView->imageData()->height();
1029  int mw = currentView->imageData()->width();
1030 
1031  if(mh % 2)
1032  mh++;
1033  if(mw % 2)
1034  mw++;
1035 
1036  QDialog customRoiDialog;
1037  QFormLayout form(&customRoiDialog);
1039 
1040  form.addRow(new QLabel(i18n("Size")));
1041 
1042  QLineEdit wle(&customRoiDialog);
1043  QLineEdit hle(&customRoiDialog);
1044 
1045  wle.setValidator(new QIntValidator(1, mw, &wle));
1046  hle.setValidator(new QIntValidator(1, mh, &hle));
1047 
1048  form.addRow(i18n("Width"), &wle);
1049  form.addRow(i18n("Height"), &hle);
1050  form.addRow(&buttonBox);
1051 
1052  connect(&buttonBox, &QDialogButtonBox::accepted, &customRoiDialog, &QDialog::accept);
1053  connect(&buttonBox, &QDialogButtonBox::rejected, &customRoiDialog, &QDialog::reject);
1054 
1055  if(customRoiDialog.exec() == QDialog::Accepted)
1056  {
1057  QPoint resetCenter = currentView->getSelectionRegion().center();
1058  int newheight = hle.text().toInt();
1059  int newwidth = wle.text().toInt();
1060 
1061  newheight = qMin(newheight, mh) ;
1062  newheight = qMax(newheight, 1) ;
1063  newwidth = qMin(newwidth, mw);
1064  newwidth = qMax(newwidth, 1);
1065 
1066  QPoint topLeft = resetCenter;
1067  QPoint botRight = resetCenter;
1068 
1069  topLeft.setX((topLeft.x() - newwidth / 2));
1070  topLeft.setY((topLeft.y() - newheight / 2));
1071  botRight.setX((botRight.x() + newwidth / 2));
1072  botRight.setY((botRight.y() + newheight / 2));
1073 
1074  emit currentView->setRubberBand(QRect(topLeft, botRight));
1075  currentView->processRectangle(topLeft, botRight, true);
1076  }
1077  }
1078 }
1079 /**
1080  This method either enables or disables the scope mouse mode so you can slew your scope to coordinates
1081  just by clicking the mouse on a spot in the image.
1082  */
1083 
1085 {
1086  QSharedPointer<FITSView> currentView;
1087  if (!getCurrentView(currentView))
1088  return;
1089 
1090  currentView->setScopeButton(actionCollection()->action("center_telescope"));
1091  if (currentView->getCursorMode() == FITSView::scopeCursor)
1092  {
1093  currentView->setCursorMode(currentView->lastMouseMode);
1094  }
1095  else
1096  {
1097  currentView->lastMouseMode = currentView->getCursorMode();
1098  currentView->setCursorMode(FITSView::scopeCursor);
1099  }
1100  updateScopeButton();
1101 }
1102 
1103 void FITSViewer::toggleCrossHair()
1104 {
1105  if (fitsTabs.empty())
1106  return;
1107 
1108  QSharedPointer<FITSView> currentView;
1109  if (!getCurrentView(currentView))
1110  return;
1111 
1112  currentView->toggleCrosshair();
1113  updateButtonStatus("view_crosshair", i18n("Cross Hairs"), currentView->isCrosshairShown());
1114 }
1115 
1116 void FITSViewer::toggleClipping()
1117 {
1118  if (fitsTabs.empty())
1119  return;
1120 
1121  QSharedPointer<FITSView> currentView;
1122  if (!getCurrentView(currentView))
1123  return;
1124  currentView->toggleClipping();
1125  if (!currentView->isClippingShown())
1126  fitsClip.clear();
1127  updateButtonStatus("view_clipping", i18n("Clipping"), currentView->isClippingShown());
1128 }
1129 
1130 void FITSViewer::toggleEQGrid()
1131 {
1132  if (fitsTabs.empty())
1133  return;
1134 
1135  QSharedPointer<FITSView> currentView;
1136  if (!getCurrentView(currentView))
1137  return;
1138 
1139  currentView->toggleEQGrid();
1140  updateButtonStatus("view_eq_grid", i18n("Equatorial Gridlines"), currentView->isEQGridShown());
1141 }
1142 
1143 void FITSViewer::toggleSelectionMode()
1144 {
1145  if (fitsTabs.empty())
1146  return;
1147 
1148  QSharedPointer<FITSView> currentView;
1149  if (!getCurrentView(currentView))
1150  return;
1151 
1152  currentView->toggleSelectionMode();
1153  updateButtonStatus("image_roi_stats", i18n("Selection Rectangle"), currentView->isSelectionRectShown());
1154 }
1155 
1156 void FITSViewer::toggleObjects()
1157 {
1158  if (fitsTabs.empty())
1159  return;
1160 
1161  QSharedPointer<FITSView> currentView;
1162  if (!getCurrentView(currentView))
1163  return;
1164 
1165  currentView->toggleObjects();
1166  updateButtonStatus("view_objects", i18n("Objects in Image"), currentView->areObjectsShown());
1167 }
1168 
1169 void FITSViewer::togglePixelGrid()
1170 {
1171  if (fitsTabs.empty())
1172  return;
1173 
1174  QSharedPointer<FITSView> currentView;
1175  if (!getCurrentView(currentView))
1176  return;
1177 
1178  currentView->togglePixelGrid();
1179  updateButtonStatus("view_pixel_grid", i18n("Pixel Gridlines"), currentView->isPixelGridShown());
1180 }
1181 
1182 void FITSViewer::toggle3DGraph()
1183 {
1184  if (fitsTabs.empty())
1185  return;
1186 
1187  QSharedPointer<FITSView> currentView;
1188  if (!getCurrentView(currentView))
1189  return;
1190 
1191  currentView->toggleStarProfile();
1192  updateButtonStatus("toggle_3D_graph", i18n("View 3D Graph"), currentView->isStarProfileShown());
1193 }
1194 
1195 void FITSViewer::toggleStars()
1196 {
1197  if (markStars)
1198  {
1199  markStars = false;
1200  actionCollection()->action("mark_stars")->setText(i18n("Mark Stars"));
1201  }
1202  else
1203  {
1204  markStars = true;
1205  actionCollection()->action("mark_stars")->setText(i18n("Unmark Stars"));
1206  }
1207 
1208  foreach (FITSTab *tab, fitsTabs)
1209  {
1210  tab->getView()->toggleStars(markStars);
1211  tab->getView()->updateFrame();
1212  }
1213 }
1214 
1215 void FITSViewer::applyFilter(int ftype)
1216 {
1217  if (fitsTabs.empty())
1218  return;
1219 
1221  updateStatusBar(i18n("Processing %1...", filterTypes[ftype - 1]), FITS_MESSAGE);
1222  qApp->processEvents();
1223  fitsTabs[fitsTabWidget->currentIndex()]->getHistogram()->applyFilter(static_cast<FITSScale>(ftype));
1224  qApp->processEvents();
1225  fitsTabs[fitsTabWidget->currentIndex()]->getView()->updateFrame();
1227  updateStatusBar(i18n("Ready."), FITS_MESSAGE);
1228 }
1229 
1230 bool FITSViewer::getView(int fitsUID, QSharedPointer<FITSView> &view)
1231 {
1232  FITSTab *tab = fitsMap.value(fitsUID);
1233  if (tab)
1234  {
1235  view = tab->getView();
1236  return true;
1237  }
1238  return false;
1239 
1240 }
1241 
1242 bool FITSViewer::getCurrentView(QSharedPointer<FITSView> &view)
1243 {
1244  if (fitsTabs.empty() || fitsTabWidget->currentIndex() >= fitsTabs.count())
1245  return false;
1246 
1247  view = fitsTabs[fitsTabWidget->currentIndex()]->getView();
1248  return true;
1249 }
1250 
1251 void FITSViewer::setDebayerAction(bool enable)
1252 {
1253  actionCollection()->addAction("fits_debayer")->setEnabled(enable);
1254 }
void setWindowIcon(const QIcon &icon)
QAction * undo(const QObject *recvr, const char *slot, QObject *parent)
QAction * addAction(const QString &name, const QObject *receiver=nullptr, const char *member=nullptr)
QUrl getOpenFileUrl(QWidget *parent, const QString &caption, const QUrl &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options, const QStringList &supportedSchemes)
AlignCenter
QAction * action(const QString &name) const
int indexOf(QWidget *w) const const
QList< QAction * > actions() const const
void setText(const QString &)
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
QString number(int n, int base)
void addAction(QAction *action)
void setDelayed(bool delayed)
virtual void reject()
QAction * redo(const QObject *recvr, const char *slot, QObject *parent)
const T value(const Key &key, const T &defaultValue) const const
int count(const T &value) const const
QAction * open(const QObject *recvr, const char *slot, QObject *parent)
void setDefaultShortcut(QAction *action, const QKeySequence &shortcut)
void setMenu(QMenu *menu)
void setCentralWidget(QWidget *widget)
QString url(QUrl::FormattingOptions options) const const
void setTabText(int index, const QString &label)
QIcon fromTheme(const QString &name)
void push_back(const T &value)
void setTabsClosable(bool closeable)
int x() const const
int y() const const
QString homePath()
void setAttribute(Qt::WidgetAttribute attribute, bool on)
bool close()
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void createGUI(const QString &xmlfile=QString())
static KStars * Instance()
Definition: kstars.h:125
QAction * zoomOut(const QObject *recvr, const char *slot, QObject *parent)
KCALUTILS_EXPORT QString errorMessage(const KCalendarCore::Exception &exception)
int remove(const Key &key)
void showMessage(const QString &message, int timeout)
bool isValid() const const
virtual void setVisible(bool visible)
QMenu * menu() const const
bool empty() const const
int size() const const
void setIcon(const QIcon &icon)
The FITSTab class holds information on the current view (drawing area) in addition to the undo/redo s...
Definition: fitstab.h:38
QAction * copy(const QObject *recvr, const char *slot, QObject *parent)
void clear()
QString i18n(const char *text, const TYPE &arg...)
void setWindowFlags(Qt::WindowFlags type)
bool isEmpty() const const
ActionType * add(const QString &name, const QObject *receiver=nullptr, const char *member=nullptr)
RemoveFilename
void setAlignment(Qt::Alignment)
int indexOf(const T &value, int from) const const
void setX(int x)
void setY(int y)
Horizontal
Primary window to view monochrome and color FITS images. The FITSviewer can open multiple images each...
Definition: fitsviewer.h:48
bool isEmpty() const const
QList< T > findChildren(const QString &name, Qt::FindChildOptions options) const const
QString fileName(QUrl::ComponentFormattingOptions options) const const
WaitCursor
int insertPermanentWidget(int index, QWidget *widget, int stretch)
void currentChanged(int index)
void setWindowTitle(const QString &)
QCoreApplication * instance()
const T & at(int i) const const
QAction * close(const QObject *recvr, const char *slot, QObject *parent)
void setText(const QString &text)
QFuture< void > filter(Sequence &sequence, KeepFunctor filterFunction)
virtual void accept()
void setDisabled(bool b)
QString toLocalFile() const const
void setCurrentWidget(QWidget *widget)
virtual int exec()
FITSViewer(QWidget *parent)
Constructor.
Definition: fitsviewer.cpp:44
This is the main window for KStars. In addition to the GUI elements, the class contains the program c...
Definition: kstars.h:92
void setEnabled(bool)
void setCheckable(bool)
T * get() const const
QString & remove(int position, int n)
QStatusBar * statusBar() const const
void show()
QString i18np(const char *singular, const char *plural, const TYPE &arg...)
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
QAction * actualSize(const QObject *recvr, const char *slot, QObject *parent)
QAction * zoomIn(const QObject *recvr, const char *slot, QObject *parent)
void resize(int w, int h)
void triggered(bool checked)
QAction * action(const char *name) const
void setColor(const QColor &color)
QString path(QUrl::ComponentFormattingOptions options) const const
virtual KActionCollection * actionCollection() const
void setEnabled(bool)
void setOverrideCursor(const QCursor &cursor)
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
int addTab(QWidget *page, const QString &label)
void clear()
void clear()
QString i18nc(const char *context, const char *text, const TYPE &arg...)
#define I18N_NOOP(text)
void setMinimumWidth(int minw)
void restoreOverrideCursor()
void centerTelescope()
This method either enables or disables the scope mouse mode so you can slew your scope to coordinates...
QAction * saveAs(const QObject *recvr, const char *slot, QObject *parent)
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
void setChecked(bool)
void updateWCSFunctions()
This is a method that either enables or disables the WCS based features in the Current View.
Definition: fitsviewer.cpp:950
ApplicationState
void addStack(QUndoStack *stack)
QString message
WA_AlwaysShowToolTips
QString tabText(int index) const const
virtual QVariant get(ScriptableExtension *callerPrincipal, quint64 objId, const QString &propName)
void tabCloseRequested(int index)
QAction * save(const QObject *recvr, const char *slot, QObject *parent)
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Tue Aug 9 2022 04:06:02 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.