As requested in http://trac.hohndel.org/ticket/563 here is a set of patches
that implements proxy support in Subsurface. Please review and let me know
if these are correct.

Also look at patch #5 as there're several open questions.

Sergey

P.S. Some new icon is required for "Network" page under Preferences.
From 15d7587bf9785effa7f5426bad810f6c976edb29 Mon Sep 17 00:00:00 2001
From: Sergey Starosek <[email protected]>
Date: Thu, 26 Jun 2014 16:44:10 +0400
Subject: [PATCH 1/5] New page "Network settings"
To: [email protected]

Signed-off-by: Sergey Starosek <[email protected]>
---
 qt-ui/preferences.ui | 305 ++++++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 244 insertions(+), 61 deletions(-)

diff --git a/qt-ui/preferences.ui b/qt-ui/preferences.ui
index 37bcb77..a2f43dd 100644
--- a/qt-ui/preferences.ui
+++ b/qt-ui/preferences.ui
@@ -120,6 +120,16 @@
          </iconset>
         </property>
        </item>
+       <item>
+        <property name="text">
+         <string>Network</string>
+        </property>
+        <property name="icon">
+         <iconset>
+          <normalon>:/round_base</normalon>
+         </iconset>
+        </property>
+       </item>
       </widget>
      </item>
      <item>
@@ -793,6 +803,147 @@
          </item>
         </layout>
        </widget>
+       <widget class="QWidget" name="page_5">
+        <layout class="QVBoxLayout" name="verticalLayout_15">
+         <item>
+          <widget class="QGroupBox" name="groupBox_10">
+           <property name="title">
+            <string>Proxy</string>
+           </property>
+           <layout class="QGridLayout" name="gridLayout_2">
+            <item row="0" column="0">
+             <widget class="QLabel" name="label_21">
+              <property name="text">
+               <string>Proxy type</string>
+              </property>
+              <property name="alignment">
+               <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+              </property>
+             </widget>
+            </item>
+            <item row="0" column="1">
+             <widget class="QComboBox" name="proxyType"/>
+            </item>
+            <item row="1" column="0">
+             <widget class="QLabel" name="label_22">
+              <property name="text">
+               <string>Host</string>
+              </property>
+             </widget>
+            </item>
+            <item row="1" column="1">
+             <widget class="QLineEdit" name="proxyHost">
+              <property name="sizePolicy">
+               <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+                <horstretch>2</horstretch>
+                <verstretch>0</verstretch>
+               </sizepolicy>
+              </property>
+              <property name="maxLength">
+               <number>64</number>
+              </property>
+             </widget>
+            </item>
+            <item row="1" column="2">
+             <widget class="QLabel" name="label_23">
+              <property name="sizePolicy">
+               <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
+                <horstretch>0</horstretch>
+                <verstretch>0</verstretch>
+               </sizepolicy>
+              </property>
+              <property name="text">
+               <string>Port</string>
+              </property>
+             </widget>
+            </item>
+            <item row="1" column="3">
+             <widget class="QSpinBox" name="proxyPort">
+              <property name="sizePolicy">
+               <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+                <horstretch>1</horstretch>
+                <verstretch>0</verstretch>
+               </sizepolicy>
+              </property>
+              <property name="maximum">
+               <number>65535</number>
+              </property>
+              <property name="value">
+               <number>80</number>
+              </property>
+             </widget>
+            </item>
+			<item row="2" column="1">
+			 <widget class="QCheckBox" name="proxyAuthRequired">
+			  <property name="layoutDirection">
+			   <enum>Qt::LeftToRight</enum>
+			  </property>
+			  <property name="text">
+			   <string>Requires authentication</string>
+			  </property>
+			 </widget>
+			</item>
+			<item row="3" column="1">
+             <widget class="QLineEdit" name="proxyUsername">
+              <property name="sizePolicy">
+               <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+                <horstretch>0</horstretch>
+                <verstretch>0</verstretch>
+               </sizepolicy>
+              </property>
+              <property name="maxLength">
+               <number>32</number>
+              </property>
+             </widget>
+            </item>
+            <item row="3" column="0">
+             <widget class="QLabel" name="label_24">
+              <property name="text">
+               <string>Username</string>
+              </property>
+             </widget>
+            </item>
+            <item row="4" column="1">
+             <widget class="QLineEdit" name="proxyPassword">
+              <property name="sizePolicy">
+               <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+                <horstretch>0</horstretch>
+                <verstretch>0</verstretch>
+               </sizepolicy>
+              </property>
+              <property name="maxLength">
+               <number>32</number>
+              </property>
+              <property name="echoMode">
+               <enum>QLineEdit::Password</enum>
+              </property>
+             </widget>
+            </item>
+            <item row="4" column="0">
+             <widget class="QLabel" name="label_25">
+              <property name="text">
+               <string>Password</string>
+              </property>
+             </widget>
+            </item>
+			<item row="5" column="0">
+			 <spacer name="verticalSpacer_3">
+			  <property name="orientation">
+			   <enum>Qt::Vertical</enum>
+			  </property>
+			  <property name="sizeHint" stdset="0">
+			   <size>
+				<width>20</width>
+				<height>40</height>
+			   </size>
+			  </property>
+			 </spacer>
+			</item>
+		   </layout>
+          </widget>
+         </item>
+        </layout>
+       </widget>
       </widget>
      </item>
     </layout>
@@ -866,12 +1017,12 @@
    <slot>setEnabled(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>845</x>
-     <y>51</y>
+     <x>185</x>
+     <y>19</y>
     </hint>
     <hint type="destinationlabel">
-     <x>308</x>
-     <y>100</y>
+     <x>186</x>
+     <y>23</y>
     </hint>
    </hints>
   </connection>
@@ -882,12 +1033,12 @@
    <slot>setDisabled(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>176</x>
-     <y>30</y>
+     <x>126</x>
+     <y>20</y>
     </hint>
     <hint type="destinationlabel">
-     <x>171</x>
-     <y>79</y>
+     <x>186</x>
+     <y>30</y>
     </hint>
    </hints>
   </connection>
@@ -898,12 +1049,12 @@
    <slot>setDisabled(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>209</x>
-     <y>34</y>
+     <x>126</x>
+     <y>20</y>
     </hint>
     <hint type="destinationlabel">
-     <x>599</x>
-     <y>33</y>
+     <x>185</x>
+     <y>20</y>
     </hint>
    </hints>
   </connection>
@@ -914,12 +1065,12 @@
    <slot>setChecked(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>504</x>
-     <y>46</y>
+     <x>164</x>
+     <y>19</y>
     </hint>
     <hint type="destinationlabel">
-     <x>623</x>
-     <y>119</y>
+     <x>175</x>
+     <y>34</y>
     </hint>
    </hints>
   </connection>
@@ -930,12 +1081,12 @@
    <slot>setChecked(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>319</x>
-     <y>46</y>
+     <x>142</x>
+     <y>19</y>
     </hint>
     <hint type="destinationlabel">
-     <x>385</x>
-     <y>119</y>
+     <x>153</x>
+     <y>34</y>
     </hint>
    </hints>
   </connection>
@@ -946,12 +1097,12 @@
    <slot>setChecked(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>504</x>
-     <y>46</y>
+     <x>164</x>
+     <y>19</y>
     </hint>
     <hint type="destinationlabel">
-     <x>623</x>
-     <y>153</y>
+     <x>175</x>
+     <y>33</y>
     </hint>
    </hints>
   </connection>
@@ -962,12 +1113,12 @@
    <slot>setChecked(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>319</x>
-     <y>46</y>
+     <x>142</x>
+     <y>19</y>
     </hint>
     <hint type="destinationlabel">
-     <x>385</x>
-     <y>153</y>
+     <x>153</x>
+     <y>33</y>
     </hint>
    </hints>
   </connection>
@@ -978,12 +1129,12 @@
    <slot>setChecked(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>504</x>
-     <y>46</y>
+     <x>164</x>
+     <y>19</y>
     </hint>
     <hint type="destinationlabel">
-     <x>623</x>
-     <y>187</y>
+     <x>175</x>
+     <y>31</y>
     </hint>
    </hints>
   </connection>
@@ -994,12 +1145,12 @@
    <slot>setChecked(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>319</x>
-     <y>46</y>
+     <x>142</x>
+     <y>19</y>
     </hint>
     <hint type="destinationlabel">
-     <x>385</x>
-     <y>187</y>
+     <x>153</x>
+     <y>31</y>
     </hint>
    </hints>
   </connection>
@@ -1010,12 +1161,12 @@
    <slot>setChecked(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>504</x>
-     <y>46</y>
+     <x>164</x>
+     <y>19</y>
     </hint>
     <hint type="destinationlabel">
-     <x>623</x>
-     <y>221</y>
+     <x>175</x>
+     <y>29</y>
     </hint>
    </hints>
   </connection>
@@ -1026,12 +1177,12 @@
    <slot>setChecked(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>319</x>
-     <y>46</y>
+     <x>142</x>
+     <y>19</y>
     </hint>
     <hint type="destinationlabel">
-     <x>385</x>
-     <y>221</y>
+     <x>153</x>
+     <y>29</y>
     </hint>
    </hints>
   </connection>
@@ -1042,12 +1193,12 @@
    <slot>setChecked(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>504</x>
-     <y>46</y>
+     <x>164</x>
+     <y>19</y>
     </hint>
     <hint type="destinationlabel">
-     <x>623</x>
-     <y>255</y>
+     <x>175</x>
+     <y>28</y>
     </hint>
    </hints>
   </connection>
@@ -1058,12 +1209,12 @@
    <slot>setChecked(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>319</x>
-     <y>46</y>
+     <x>142</x>
+     <y>19</y>
     </hint>
     <hint type="destinationlabel">
-     <x>385</x>
-     <y>255</y>
+     <x>153</x>
+     <y>28</y>
     </hint>
    </hints>
   </connection>
@@ -1074,12 +1225,12 @@
    <slot>setValue(int)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>718</x>
-     <y>415</y>
+     <x>181</x>
+     <y>319</y>
     </hint>
     <hint type="destinationlabel">
-     <x>823</x>
-     <y>414</y>
+     <x>798</x>
+     <y>314</y>
     </hint>
    </hints>
   </connection>
@@ -1090,12 +1241,44 @@
    <slot>setValue(int)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>790</x>
-     <y>400</y>
+     <x>798</x>
+     <y>314</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>181</x>
+     <y>319</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>proxyAuthRequired</sender>
+   <signal>toggled(bool)</signal>
+   <receiver>proxyUsername</receiver>
+   <slot>setEnabled(bool)</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>211</x>
+     <y>122</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>257</x>
+     <y>151</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>proxyAuthRequired</sender>
+   <signal>toggled(bool)</signal>
+   <receiver>proxyPassword</receiver>
+   <slot>setEnabled(bool)</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>230</x>
+     <y>124</y>
     </hint>
     <hint type="destinationlabel">
-     <x>580</x>
-     <y>417</y>
+     <x>319</x>
+     <y>187</y>
     </hint>
    </hints>
   </connection>
@@ -1104,9 +1287,9 @@
   <buttongroup name="verticalSpeed"/>
   <buttongroup name="buttonGroup_2"/>
   <buttongroup name="buttonGroup_3"/>
-  <buttongroup name="buttonGroup"/>
   <buttongroup name="buttonGroup_4"/>
   <buttongroup name="buttonGroup_5"/>
   <buttongroup name="buttonGroup_6"/>
+  <buttongroup name="buttonGroup"/>
  </buttongroups>
 </ui>
-- 
1.8.5.5

From c8ce0bf5b4d1ca780ffa0594c2ef5ed583ec24cb Mon Sep 17 00:00:00 2001
From: Sergey Starosek <[email protected]>
Date: Thu, 26 Jun 2014 16:45:14 +0400
Subject: [PATCH 2/5] UI setup for "Network settings" page
To: [email protected]

Signed-off-by: Sergey Starosek <[email protected]>
---
 qt-ui/preferences.cpp | 23 +++++++++++++++++++++++
 qt-ui/preferences.h   |  1 +
 2 files changed, 24 insertions(+)

diff --git a/qt-ui/preferences.cpp b/qt-ui/preferences.cpp
index fe4c872..d0880d6 100644
--- a/qt-ui/preferences.cpp
+++ b/qt-ui/preferences.cpp
@@ -6,6 +6,7 @@
 #include <QMessageBox>
 #include <QSortFilterProxyModel>
 #include <QShortcut>
+#include <QNetworkProxy>
 
 PreferencesDialog *PreferencesDialog::instance()
 {
@@ -18,6 +19,12 @@ PreferencesDialog *PreferencesDialog::instance()
 PreferencesDialog::PreferencesDialog(QWidget *parent, Qt::WindowFlags f) : QDialog(parent, f)
 {
 	ui.setupUi(this);
+	ui.proxyType->clear();
+	ui.proxyType->addItem(tr("No proxy"), QNetworkProxy::NoProxy);
+	ui.proxyType->addItem(tr("System proxy"), QNetworkProxy::DefaultProxy);
+	ui.proxyType->addItem(tr("HTTP proxy"), QNetworkProxy::HttpProxy);
+	ui.proxyType->addItem(tr("SOCKS proxy"), QNetworkProxy::Socks5Proxy);
+	connect(ui.proxyType, SIGNAL(currentIndexChanged(int)), this, SLOT(on_proxyType_changed(int)));
 	connect(ui.buttonBox, SIGNAL(clicked(QAbstractButton *)), this, SLOT(buttonClicked(QAbstractButton *)));
 	connect(ui.gflow, SIGNAL(valueChanged(int)), this, SLOT(gflowChanged(int)));
 	connect(ui.gfhigh, SIGNAL(valueChanged(int)), this, SLOT(gfhighChanged(int)));
@@ -378,3 +385,19 @@ void PreferencesDialog::emitSettingsChanged()
 {
 	emit settingsChanged();
 }
+
+void PreferencesDialog::on_proxyType_changed(int idx)
+{
+	if (idx == -1) {
+		return;
+	}
+
+	int proxyType = ui.proxyType->itemData(idx).toInt();
+	bool hpEnabled = (proxyType == QNetworkProxy::Socks5Proxy || proxyType == QNetworkProxy::HttpProxy);
+	ui.proxyHost->setEnabled(hpEnabled);
+	ui.proxyPort->setEnabled(hpEnabled);
+	ui.proxyAuthRequired->setEnabled(hpEnabled);
+	ui.proxyUsername->setEnabled(hpEnabled & ui.proxyAuthRequired->isChecked());
+	ui.proxyPassword->setEnabled(hpEnabled & ui.proxyAuthRequired->isChecked());
+	ui.proxyAuthRequired->setChecked(ui.proxyAuthRequired->isChecked());
+}
diff --git a/qt-ui/preferences.h b/qt-ui/preferences.h
index 474064b..a32c725 100644
--- a/qt-ui/preferences.h
+++ b/qt-ui/preferences.h
@@ -28,6 +28,7 @@ slots:
 	void rememberPrefs();
 	void gflowChanged(int gf);
 	void gfhighChanged(int gf);
+	void on_proxyType_changed(int idx);
 
 private:
 	explicit PreferencesDialog(QWidget *parent = 0, Qt::WindowFlags f = 0);
-- 
1.8.5.5

From 7384aa24f60dae1f79f5c70b6805946568a3f435 Mon Sep 17 00:00:00 2001
From: Sergey Starosek <[email protected]>
Date: Thu, 26 Jun 2014 20:16:55 +0400
Subject: [PATCH 3/5] Expose proxy settings into preferences struct
To: [email protected]

Signed-off-by: Sergey Starosek <[email protected]>
---
 pref.h | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/pref.h b/pref.h
index 0764418..6feaa76 100644
--- a/pref.h
+++ b/pref.h
@@ -53,6 +53,12 @@ struct preferences {
 	int descrate;
 	int bottompo2;
 	int decopo2;
+	int proxy_type;
+	char *proxy_host;
+	int proxy_port;
+	short proxy_auth;
+	char *proxy_user;
+	char *proxy_pass;
 };
 enum unit_system_values {
 	METRIC,
-- 
1.8.5.5

From 21023dbeac662b68e3006fa8cf0326baf5f2b08d Mon Sep 17 00:00:00 2001
From: Sergey Starosek <[email protected]>
Date: Thu, 26 Jun 2014 20:17:50 +0400
Subject: [PATCH 4/5] Implement proxy settings loading/saving
To: [email protected]

- proxy settings are stored under [Network] group
- default is "No proxy"
- duplicate #def GET_TXT replaced with GET_INT_DEF

Signed-off-by: Sergey Starosek <[email protected]>
---
 qt-ui/preferences.cpp | 33 ++++++++++++++++++++++++++++++---
 1 file changed, 30 insertions(+), 3 deletions(-)

diff --git a/qt-ui/preferences.cpp b/qt-ui/preferences.cpp
index d0880d6..9a55507 100644
--- a/qt-ui/preferences.cpp
+++ b/qt-ui/preferences.cpp
@@ -24,6 +24,7 @@ PreferencesDialog::PreferencesDialog(QWidget *parent, Qt::WindowFlags f) : QDial
 	ui.proxyType->addItem(tr("System proxy"), QNetworkProxy::DefaultProxy);
 	ui.proxyType->addItem(tr("HTTP proxy"), QNetworkProxy::HttpProxy);
 	ui.proxyType->addItem(tr("SOCKS proxy"), QNetworkProxy::Socks5Proxy);
+	ui.proxyType->setCurrentIndex(-1);
 	connect(ui.proxyType, SIGNAL(currentIndexChanged(int)), this, SLOT(on_proxyType_changed(int)));
 	connect(ui.buttonBox, SIGNAL(clicked(QAbstractButton *)), this, SLOT(buttonClicked(QAbstractButton *)));
 	connect(ui.gflow, SIGNAL(valueChanged(int)), this, SLOT(gflowChanged(int)));
@@ -124,6 +125,13 @@ void PreferencesDialog::setUiFromPrefs()
 		ui.languageView->setCurrentIndex(languages.first());
 
 	s.endGroup();
+
+	ui.proxyHost->setText(prefs.proxy_host);
+	ui.proxyPort->setValue(prefs.proxy_port);
+	ui.proxyAuthRequired->setChecked(prefs.proxy_auth);
+	ui.proxyUsername->setText(prefs.proxy_user);
+	ui.proxyPassword->setText(prefs.proxy_pass);
+	ui.proxyType->setCurrentIndex(ui.proxyType->findData(prefs.proxy_type));
 }
 
 void PreferencesDialog::restorePrefs()
@@ -168,12 +176,12 @@ void PreferencesDialog::rememberPrefs()
 	else                             \
 		prefs.field = default_prefs.field
 
-#define GET_TXT(name, field)                                             \
+#define GET_INT_DEF(name, field, defval)                                             \
 	v = s.value(QString(name));                                      \
 	if (v.isValid())                                                 \
-		prefs.field = strdup(v.toString().toUtf8().constData()); \
+		prefs.field = v.toInt(); \
 	else                                                             \
-		prefs.field = default_prefs.field
+		prefs.field = defval
 
 #define GET_TXT(name, field)                                             \
 	v = s.value(QString(name));                                      \
@@ -246,6 +254,15 @@ void PreferencesDialog::syncSettings()
 	s.setValue("animation_speed", ui.velocitySlider->value());
 	s.endGroup();
 
+	s.beginGroup("Network");
+	s.setValue("proxy_type", ui.proxyType->itemData(ui.proxyType->currentIndex()).toInt());
+	s.setValue("proxy_host", ui.proxyHost->text());
+	s.setValue("proxy_port", ui.proxyPort->value());
+	SB("proxy_auth", ui.proxyAuthRequired);
+	s.setValue("proxy_user", ui.proxyUsername->text());
+	s.setValue("proxy_pass", ui.proxyPassword->text());
+	s.endGroup();
+
 	loadSettings();
 	emit settingsChanged();
 }
@@ -325,6 +342,16 @@ void PreferencesDialog::loadSettings()
 
 	s.beginGroup("Animations");
 	GET_INT("animation_speed", animation);
+	s.endGroup();
+
+	s.beginGroup("Network");
+	GET_INT_DEF("proxy_type", proxy_type, QNetworkProxy::NoProxy);
+	GET_TXT("proxy_host", proxy_host);
+	GET_INT("proxy_port", proxy_port);
+	GET_BOOL("proxy_auth", proxy_auth);
+	GET_TXT("proxy_user", proxy_user);
+	GET_TXT("proxy_pass", proxy_pass);
+	s.endGroup();
 }
 
 void PreferencesDialog::buttonClicked(QAbstractButton *button)
-- 
1.8.5.5

From 4ff8fd6ca24ed2b102039cb9021596fdaa929fbe Mon Sep 17 00:00:00 2001
From: Sergey Starosek <[email protected]>
Date: Thu, 26 Jun 2014 20:20:34 +0400
Subject: [PATCH 5/5] Implement proxy re-configuration
To: [email protected]

- application level proxy is reconfigured on settings saving
- tested with direct connection (no proxy), local proxy without auth
  (tinyproxy) and SOCKS (ssh -D dynamic port forwarding)
- not sure about QNetworkProxy reuse between invocations
- consider using QNetworkProxyFactory (but since no plain TCP
  connections are used, QNetworkProxy seems to be good choice)

Signed-off-by: Sergey Starosek <[email protected]>
---
 qt-ui/mainwindow.cpp | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/qt-ui/mainwindow.cpp b/qt-ui/mainwindow.cpp
index 13a4413..c174cc6 100644
--- a/qt-ui/mainwindow.cpp
+++ b/qt-ui/mainwindow.cpp
@@ -51,6 +51,7 @@
 #ifndef NO_USERMANUAL
 #include "usermanual.h"
 #endif
+#include <QNetworkProxy>
 
 MainWindow *MainWindow::m_Instance = NULL;
 
@@ -823,6 +824,18 @@ void MainWindow::readSettings()
 	default_dive_computer_product = getSetting(s, "dive_computer_product");
 	default_dive_computer_device = getSetting(s, "dive_computer_device");
 	s.endGroup();
+
+	QNetworkProxy proxy;
+	proxy.setType(QNetworkProxy::ProxyType(prefs.proxy_type));
+	proxy.setHostName(prefs.proxy_host);
+	proxy.setPort(prefs.proxy_port);
+	if (prefs.proxy_auth) {
+		proxy.setUser(prefs.proxy_user);
+		proxy.setPassword(prefs.proxy_pass);
+	}
+	QNetworkProxy::setApplicationProxy(proxy);
+
+
 	loadRecentFiles(&s);
 	checkSurvey(&s);
 }
-- 
1.8.5.5

_______________________________________________
subsurface mailing list
[email protected]
http://lists.hohndel.org/cgi-bin/mailman/listinfo/subsurface

Reply via email to