/* * Copyright (c) 2007, 2008 Harry Bock * Copyright (c) 2007 Gustavo Pichorim Boiko * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "outputconfig.h" #include "outputgraphicsitem.h" #include "collapsiblewidget.h" #include "layoutmanager.h" #include "randrconfig.h" #include "randroutput.h" #include "randrdisplay.h" #include "randrscreen.h" #include #include #include #include QT_BEGIN_NAMESPACE uint qHash( const QPoint& p ) { return p.x() * 10000 + p.y(); } QT_END_NAMESPACE RandRConfig::RandRConfig(QWidget *parent, RandRDisplay *display) : QWidget(parent), Ui::RandRConfigBase() { m_display = display; Q_ASSERT(m_display); if (!m_display->isValid()) { // FIXME: this needs much better handling of this error... return; } setupUi(this); layout()->setMargin(0); connect( identifyOutputsButton, SIGNAL(clicked()), SLOT(identifyOutputs())); connect( &identifyTimer, SIGNAL(timeout()), SLOT(clearIndicators())); connect( &compressUpdateViewTimer, SIGNAL(timeout()), SLOT(slotDelayedUpdateView())); connect(unifyOutputs, SIGNAL(toggled(bool)), SLOT(unifiedOutputChanged(bool))); identifyTimer.setSingleShot( true ); compressUpdateViewTimer.setSingleShot( true ); connect( saveAsDefaultButton, SIGNAL(clicked()), SLOT(saveStartup())); QMenu* saveMenu = new QMenu(saveAsDefaultButton); saveMenu->addAction(i18n("Save as Default"),this, SLOT(saveStartup())); saveMenu->addAction(i18n("Reset"),this, SLOT(disableStartup())); saveAsDefaultButton->setMenu(saveMenu); // create the container for the settings widget QHBoxLayout *layout = new QHBoxLayout(outputList); layout->setSpacing(0); layout->setContentsMargins(0,0,0,0); m_container = new SettingsContainer(outputList); m_container->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); layout->addWidget(m_container); #ifdef HAS_RANDR_1_3 if (RandR::has_1_3) { primaryDisplayBox->setVisible(true); label->setVisible(true); } else #endif //HAS_RANDR_1_3 { primaryDisplayBox->setVisible(false); label->setVisible(false); } KConfig config("krandrrc"); if (config.hasGroup("Screen_0") && config.group("Screen_0").readEntry("OutputsUnified", false)) { unifyOutputs->setChecked(true); } // create the scene m_scene = new QGraphicsScene(m_display->currentScreen()->rect(), screenView); screenView->setScene(m_scene); screenView->installEventFilter(this); m_layoutManager = new LayoutManager(m_display->currentScreen(), m_scene); } RandRConfig::~RandRConfig() { clearIndicators(); } void RandRConfig::load(void) { if (!m_display->isValid()) { kDebug() << "Invalid display! Aborting config load."; return; } m_scene->clear(); qDeleteAll(m_outputList); m_outputList.clear(); m_configs.clear(); // objects deleted above OutputMap outputs = m_display->currentScreen()->outputs(); #ifdef HAS_RANDR_1_3 RandROutput *primary = m_display->currentScreen()->primaryOutput(); if (RandR::has_1_3) { // disconnect while we repopulate the combo box disconnect(primaryDisplayBox, SIGNAL(currentIndexChanged(int)), this, SIGNAL(changed())); disconnect(primaryDisplayBox, SIGNAL(currentIndexChanged(int)), this, SLOT(updatePrimaryDisplay())); primaryDisplayBox->clear(); primaryDisplayBox->addItem(i18nc("No display selected", "None")); } #endif //HAS_RANDR_1_3 // FIXME: adjust it to run on a multi screen system CollapsibleWidget *w; OutputGraphicsItem *o; OutputConfigList preceding; foreach(RandROutput *output, outputs) { OutputConfig *config = new OutputConfig(this, output, preceding, unifyOutputs->isChecked()); m_configs.append( config ); preceding.append( config ); QString description = output->isConnected() ? i18n("%1 (Connected)", output->name()) : output->name(); w = m_container->insertWidget(config, description); if(output->isConnected()) { w->setExpanded(true); kDebug() << "Output rect:" << output->rect(); } connect(config, SIGNAL(connectedChanged(bool)), this, SLOT(outputConnectedChanged(bool))); m_outputList.append(w); o = new OutputGraphicsItem(config); m_scene->addItem(o); connect(o, SIGNAL(itemChanged(OutputGraphicsItem*)), this, SLOT(slotAdjustOutput(OutputGraphicsItem*))); connect(config, SIGNAL(updateView()), this, SLOT(slotUpdateView())); connect(config, SIGNAL(optionChanged()), this, SIGNAL(changed())); #ifdef HAS_RANDR_1_3 if (RandR::has_1_3 && output->isConnected()) { primaryDisplayBox->addItem(output->name(), QVariant::fromValue(output->id())); if (primary == output) { primaryDisplayBox->setCurrentIndex(primaryDisplayBox->count()-1); } } #endif //HAS_RANDR_1_3 } #ifdef HAS_RANDR_1_3 if (RandR::has_1_3) { connect(primaryDisplayBox, SIGNAL(currentIndexChanged(int)), this, SIGNAL(changed())); connect(primaryDisplayBox, SIGNAL(currentIndexChanged(int)), this, SLOT(updatePrimaryDisplay())); } #endif //HAS_RANDR_1_3 slotUpdateView(); } void RandRConfig::outputConnectedChanged(bool connected) { OutputConfig *config = static_cast (sender()); int index = m_configs.indexOf(config); QString description = connected ? i18n("%1 (Connected)", config->output()->name()) : config->output()->name(); m_outputList.at(index)->setCaption(description); } void RandRConfig::save() { if (!m_display->isValid()) return; KConfig config("krandrrc"); if (config.hasGroup("Screen_0")) { config.group("Screen_0").writeEntry("OutputsUnified", unifyOutputs->isChecked()); config.sync(); } apply(); } void RandRConfig::defaults() { update(); } void RandRConfig::apply() { kDebug() << "Applying settings..."; // normalize positions so that the coordinate system starts at (0,0) QPoint normalizePos; bool first = true; foreach(CollapsibleWidget *w, m_outputList) { OutputConfig *config = static_cast(w->innerWidget()); if( config->isActive()) { QPoint pos = config->position(); if( first ) { normalizePos = pos; first = false; } else { if( pos.x() < normalizePos.x()) normalizePos.setX( pos.x()); if( pos.y() < normalizePos.y()) normalizePos.setY( pos.y()); } } } normalizePos = -normalizePos; kDebug() << "Normalizing positions by" << normalizePos; foreach(CollapsibleWidget *w, m_outputList) { OutputConfig *config = static_cast(w->innerWidget()); RandROutput *output = config->output(); if(!output->isConnected()) continue; QSize res = config->resolution(); if(!res.isNull()) { if(!config->hasPendingChanges( normalizePos )) { kDebug() << "Ignoring identical config for" << output->name(); continue; } QRect configuredRect(config->position(), res); kDebug() << "Output config for" << output->name() << ":\n" " rect =" << configuredRect << ", rot =" << config->rotation() << ", rate =" << config->refreshRate(); // Break the connection with the previous CRTC for changed outputs, since // otherwise the code could try to use the same CRTC for two different outputs. // This is probably rather hackish and may not always work, but I don't see // a better way with this codebase, definitely not with the time I have now. output->disconnectFromCrtc(); output->proposeRect(configuredRect.translated( normalizePos )); output->proposeRotation(config->rotation()); output->proposeRefreshRate(config->refreshRate()); } else { // user wants to disable this output kDebug() << "Disabling" << output->name(); output->slotDisable(); } } #ifdef HAS_RANDR_1_3 if (RandR::has_1_3) { int primaryOutputIndex = primaryDisplayBox->currentIndex(); RandRScreen *screen = m_display->currentScreen(); if (primaryOutputIndex > 0) { QVariant output = primaryDisplayBox->itemData(primaryOutputIndex); screen->proposePrimaryOutput(screen->output(output.value())); } else { screen->proposePrimaryOutput(0); } } #endif //HAS_RANDR_1_3 m_display->applyProposed(); update(); } void RandRConfig::updatePrimaryDisplay() { QString primary=primaryDisplayBox->currentText(); foreach( QGraphicsItem* item, m_scene->items()) { OutputGraphicsItem* itemo = dynamic_cast< OutputGraphicsItem* >( item ); if(itemo && (itemo->objectName()==primary)!=itemo->isPrimary()) { itemo->setPrimary(itemo->objectName()==primary); } } } void RandRConfig::update() { // TODO: implement emit changed(false); } void RandRConfig::saveStartup() { if (!m_display->isValid()) return; KConfig config("krandrrc"); m_display->saveStartup(config); #ifdef HAS_RANDR_1_3 if (RandR::has_1_3) { // Add setting the primary screen to the list of commands KConfigGroup group = config.group("Display"); QStringList commands = group.readEntry("StartupCommands").split("\n"); int primaryOutputIndex = primaryDisplayBox->currentIndex(); if (primaryOutputIndex > 0) { QString primaryOutput = primaryDisplayBox->itemText(primaryOutputIndex); commands += QString("xrandr --output %1 --primary") .arg( KShell::quoteArg( primaryOutput )); } else commands += "xrandr --noprimary"; group.writeEntry("StartupCommands",commands.join("\n")); } #endif //HAS_RANDR_1_3 KMessageBox::information( window(), i18n( "Configuration has been set as the desktop default." )); } void RandRConfig::disableStartup() { if (!m_display->isValid()) return; KConfig config("krandrrc"); m_display->disableStartup(config); KMessageBox::information( window(), i18n( "Default desktop setup has been reset." )); } void RandRConfig::unifiedOutputChanged(bool checked) { Q_FOREACH(OutputConfig *config, m_configs) { config->setUnifyOutput(checked); config->updateSizeList(); } emit changed(true); } bool RandRConfig::eventFilter(QObject *obj, QEvent *event) { if ( obj == screenView && event->type() == QEvent::Resize ) { slotUpdateView(); return false; } else { return QWidget::eventFilter(obj, event); } } void RandRConfig::slotAdjustOutput(OutputGraphicsItem *o) { Q_UNUSED(o); kDebug() << "Output graphics item changed:"; // TODO: Implement } void RandRConfig::slotUpdateView() { compressUpdateViewTimer.start( 0 ); } void RandRConfig::slotDelayedUpdateView() { QRect r; bool first = true; // updates the graphics view so that all outputs fit inside of it foreach(OutputConfig *config, m_configs) { if (first) { first = false; r = config->rect(); } else r = r.united(config->rect()); } // scale the total bounding rectangle for all outputs to fit // 80% of the containing QGraphicsView float scaleX = (float)screenView->width() / r.width(); float scaleY = (float)screenView->height() / r.height(); float scale = (scaleX < scaleY) ? scaleX : scaleY; scale *= 0.80f; screenView->resetMatrix(); screenView->scale(scale,scale); screenView->ensureVisible(r); screenView->setSceneRect(r); foreach( QGraphicsItem* item, m_scene->items()) { if( OutputGraphicsItem* itemo = dynamic_cast< OutputGraphicsItem* >( item )) itemo->configUpdated(); } updatePrimaryDisplay(); screenView->update(); } void RandRConfig::identifyOutputs() { identifyTimer.stop(); clearIndicators(); QHash< QPoint, QStringList > ids; // outputs at centers of screens (can be more in case of clone mode) OutputMap outputs = m_display->currentScreen()->outputs(); foreach(RandROutput *output, outputs) { if( !output->isConnected() || output->rect().isEmpty()) continue; ids[ output->rect().center() ].append( output->name()); } for( QHash< QPoint, QStringList >::ConstIterator it = ids.constBegin(); it != ids.constEnd(); ++it ) { QLabel *si = new QLabel(it->join("\n"), NULL, Qt::X11BypassWindowManagerHint); QFont fnt = KGlobalSettings::generalFont(); fnt.setPixelSize(100); si->setFont(fnt); si->setFrameStyle(QFrame::Panel); si->setFrameShadow(QFrame::Plain); si->setAlignment(Qt::AlignCenter); QRect targetGeometry(QPoint(0,0), si->sizeHint()); targetGeometry.moveCenter(it.key()); si->setGeometry(targetGeometry); si->show(); m_indicators.append( si ); } identifyTimer.start( 1500 ); } void RandRConfig::clearIndicators() { qDeleteAll( m_indicators ); m_indicators.clear(); } #include "moc_randrconfig.cpp" // vim:noet:sts=8:sw=8: