From fc99eb0214271ab3f0e2dddd7db44ffc1513b9a1 Mon Sep 17 00:00:00 2001
From: marco <marco@5a81b35b-ba03-0410-adc8-b2c5c5119f08>
Date: Wed, 28 Jul 2010 13:19:10 +0000
Subject: [PATCH] added install command-line tool dialog

a dialog pop-up the first time a user starts DNG on the Mac and
asks if symlinks for the command-line tools should be created.

git-svn-id: https://dng.biozentrum.unibas.ch/svn/openstructure/trunk@2595 5a81b35b-ba03-0410-adc8-b2c5c5119f08
---
 modules/gui/pymod/CMakeLists.txt  |  6 ++
 modules/gui/pymod/dng/__init__.py |  0
 modules/gui/pymod/dng/termuse.py  | 70 ++++++++++++++++++++++
 modules/gui/pymod/init_menubar.py |  7 ++-
 modules/gui/pymod/init_splash.py  |  2 +-
 modules/gui/pymod/wrap_gui.cc     |  9 ++-
 modules/gui/src/CMakeLists.txt    | 14 ++++-
 modules/gui/src/admin.cc          | 98 +++++++++++++++++++++++++++++++
 modules/gui/src/admin.hh          | 49 ++++++++++++++++
 scripts/init.py                   |  9 ++-
 10 files changed, 258 insertions(+), 6 deletions(-)
 create mode 100644 modules/gui/pymod/dng/__init__.py
 create mode 100644 modules/gui/pymod/dng/termuse.py
 create mode 100644 modules/gui/src/admin.cc
 create mode 100644 modules/gui/src/admin.hh

diff --git a/modules/gui/pymod/CMakeLists.txt b/modules/gui/pymod/CMakeLists.txt
index 2d36fa75b..d6ceebcff 100644
--- a/modules/gui/pymod/CMakeLists.txt
+++ b/modules/gui/pymod/CMakeLists.txt
@@ -84,8 +84,14 @@ set(OST_GUI_PYMOD_MODULES
   init_splash.py
 )
 
+set(OST_GUI_PYMOD_DNG_MODULES
+  __init__.py
+  termuse.py
+)
+
 pymod(NAME gui CPP ${OST_GUI_PYMOD_SOURCES} 
       PY ${OST_GUI_SCENE_PYMOD_MODULES} IN_DIR scene
+         ${OST_GUI_PYMOD_DNG_MODULES} IN_DIR dng
          ${OST_GUI_PYMOD_MODULES})
 
 set(PRESET_FILES
diff --git a/modules/gui/pymod/dng/__init__.py b/modules/gui/pymod/dng/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/modules/gui/pymod/dng/termuse.py b/modules/gui/pymod/dng/termuse.py
new file mode 100644
index 000000000..13341a0ca
--- /dev/null
+++ b/modules/gui/pymod/dng/termuse.py
@@ -0,0 +1,70 @@
+from PyQt4.QtGui import *
+from PyQt4.QtCore import *
+from ost.gui import AdminRights
+import ost
+import os
+#from ost.gui import AdminRights
+
+usage='''The DNG application bundle contains two shell commands ('ost' and 'dng') that lets you use the OpenStructure command-line interpreter and dng from the terminal. If you want to use these commands, it is recommended that a symbolic link is created. 
+'''
+class TerminalUsageDialog(QDialog):
+  def __init__(self, parent=None):
+    QDialog.__init__(self, parent)
+    self.setWindowTitle('Enhanced Terminal Usage')
+    self.setFixedSize(QSize(480, 300))
+    l=QVBoxLayout(self)
+    title=QLabel('Enhanced Terminal Usage')
+    font=title.font()
+    font.setPointSize(20)
+    title.setFont(font)
+    l.addWidget(title)
+    text=QLabel(usage)
+    l.addWidget(text)
+    l2=QHBoxLayout()
+    l2.addWidget(QLabel('If you proceed, the link will be created in: '))
+    self.path_combo=QComboBox()
+    self.path_combo.setFixedWidth(150)
+    for path in os.getenv('PATH').split(':'):
+      if os.path.exists(os.path.expanduser(path)):
+        self.path_combo.addItem(path)
+    l2.addWidget(self.path_combo)
+    l.addLayout(l2)
+    l3=QHBoxLayout()
+    ab=QPushButton('Create Link')
+    ab.setDefault(True)
+    cb=QPushButton('Don\'t Create')
+    l3.addStretch(1)
+    l3.addWidget(cb, 0)
+    l3.addWidget(ab, 0)
+    l.addLayout(l3)
+    text.setWordWrap(True)
+    QObject.connect(cb, SIGNAL('clicked()'), self.reject)
+    QObject.connect(ab, SIGNAL('clicked()'), self.accept)    
+  def GetSelectedPath(self):
+    return str(self.path_combo.currentText())
+
+def _CreateLinks(bin_dir, sel_dir):
+  for bin in ('ost', 'dng',):
+    if os.path.exists(os.path.join(sel_dir, bin)):
+      os.unlink(os.path.join(sel_dir, bin))
+    os.system('ln -s "%s" "%s"' % (os.path.join(bin_dir, bin), 
+                                   os.path.join(sel_dir, bin)))
+def InstallTerminalPrograms():
+  """
+  Installs symlinks to the 'ost' and 'dng' command line programs into a 
+  user-specified directory in the path.
+  """  
+  term_use=TerminalUsageDialog()
+  if term_use.exec_():
+    prefix=ost.GetPrefixPath()
+    bin_dir=os.path.join(prefix, 'bin')
+    sel_path=term_use.GetSelectedPath()    
+    if not os.access(sel_path, os.W_OK):
+      admin_rights=AdminRights()
+      if admin_rights.Acquire():
+        for bin in ('ost', 'dng'):
+          admin_rights.CreateLink(os.path.join(bin_dir, bin), 
+                                  os.path.join(sel_path, bin))
+      admin_rights.Release()
+    else:
+      _CreateLinks(bin_dir, sel_path)
diff --git a/modules/gui/pymod/init_menubar.py b/modules/gui/pymod/init_menubar.py
index d1c33cfae..ac5c23e23 100644
--- a/modules/gui/pymod/init_menubar.py
+++ b/modules/gui/pymod/init_menubar.py
@@ -28,7 +28,7 @@ from ost.gui import FileLoader
 from ost.gui.scene.file_loader import GenericLoader
 from ost.gui.scene.loader_manager_widget import LoaderManagerWidget
 from ost.gui.init_splash import _InitSplash
-
+from ost.gui.dng import termuse
 class InitMenuBar(QtCore.QObject):
   def __init__(self, menu_bar=None):
     QtCore.QObject.__init__(self, menu_bar)
@@ -50,6 +50,11 @@ class InitMenuBar(QtCore.QObject):
     webpage.setShortcut('Ctrl+D')
     self.connect(webpage, QtCore.SIGNAL('triggered()'), self.OpenDocs)
     help.addAction(webpage)
+    if sys.platform=='darwin':
+      install_ctl=QtGui.QAction('Install Command Line Tool', self)    
+      self.connect(install_ctl, QtCore.SIGNAL('triggered()'), 
+                   termuse.InstallTerminalPrograms)
+      help.addAction(install_ctl)
     about = QtGui.QAction('&About', self)
     about.setStatusTip('About')
     about.setShortcut('Ctrl+A')
diff --git a/modules/gui/pymod/init_splash.py b/modules/gui/pymod/init_splash.py
index 2941c858d..332636063 100644
--- a/modules/gui/pymod/init_splash.py
+++ b/modules/gui/pymod/init_splash.py
@@ -22,6 +22,6 @@ class SplashDialog(QtGui.QDialog):
     
 def _InitSplash():
   splash = SplashDialog(gui.GostyApp.Instance().perspective.main_area.qobject)
-  splash.showNormal()
+  splash.exec_()
 
   #QtCore.QTimer.singleShot(30000, splash.close);
diff --git a/modules/gui/pymod/wrap_gui.cc b/modules/gui/pymod/wrap_gui.cc
index 53dffcc83..712d4ce4e 100644
--- a/modules/gui/pymod/wrap_gui.cc
+++ b/modules/gui/pymod/wrap_gui.cc
@@ -21,7 +21,7 @@
 #include <boost/python.hpp>
 #include <QString>
 #include <ost/config.hh>
-
+#include <ost/gui/admin.hh>
 using namespace boost::python;
 
 void export_AlignmentView();
@@ -97,6 +97,7 @@ namespace {
 
 
 }
+using namespace ost::gui;
 
 BOOST_PYTHON_MODULE(_gui)
 {
@@ -131,4 +132,10 @@ BOOST_PYTHON_MODULE(_gui)
   export_overlay();
   export_overlay_manager();
   #endif
+  
+  class_<AdminRights>("AdminRights", init<>())
+    .def("Acquire", &AdminRights::Acquire)
+    .def("Release", &AdminRights::Release)
+    .def("CreateLink", &AdminRights::CreateLink)
+  ;
 }
diff --git a/modules/gui/src/CMakeLists.txt b/modules/gui/src/CMakeLists.txt
index b4ba16d2d..9504b73d8 100644
--- a/modules/gui/src/CMakeLists.txt
+++ b/modules/gui/src/CMakeLists.txt
@@ -169,6 +169,7 @@ gl_canvas.hh
 gl_win.hh
 scene_menu.hh
 gosty_app.hh
+admin.hh
 main.hh
 main_area.hh
 module_config.hh
@@ -204,6 +205,7 @@ main_window.cc
 gl_win.cc
 scene_menu.cc
 widget.cc
+admin.cc
 widget_pool.cc
 remote_site_loader.cc
 sequence_viewer/align_properties_painter.cc
@@ -458,7 +460,9 @@ if (ENABLE_SPNAV)
       input/spnav_input.hh
   )
 endif()
-
+if (APPLE)
+  set(ADDITIONAL_LIBRARIES "-framework Security")
+endif()
 set(QT_USE_QTOPENGL 1)
 set(QT_USE_QTNETWORK 1)
 include(${QT_USE_FILE})
@@ -474,7 +478,13 @@ module(NAME gui SOURCES ${OST_GUI_MOCS} ${OST_GUI_SOURCES}
                ${OST_GUI_DATA_VIEWER_HEADERS}
                ${OST_GUI_HEADERS}
        DEPENDS_ON gfx io mol_alg seq_alg
-       LINK ${QT_LIBRARIES} ${PYTHON_LIBRARIES} ${BOOST_PYTHON_LIBRARIES} ${SPNAV_LIBRARIES})
+       LINK ${QT_LIBRARIES} ${PYTHON_LIBRARIES} ${BOOST_PYTHON_LIBRARIES}
+            ${SPNAV_LIBRARIES})
+            
+if (ADDITIONAL_LIBRARIES)
+  target_link_libraries(ost_gui "${ADDITIONAL_LIBRARIES}")
+endif()
+
 include_directories(${PYTHON_INCLUDE_PATH})
 qt4_add_resources(OST_QT_RESOURCE dngr.qrc)
 qt4_wrap_cpp(OST_GOSTY_MOC "gosty.hh")
diff --git a/modules/gui/src/admin.cc b/modules/gui/src/admin.cc
new file mode 100644
index 000000000..2b3de2da8
--- /dev/null
+++ b/modules/gui/src/admin.cc
@@ -0,0 +1,98 @@
+//------------------------------------------------------------------------------
+// This file is part of the OpenStructure project <www.openstructure.org>
+//
+// Copyright (C) 2008-2010 by the OpenStructure authors
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License as published by the Free
+// Software Foundation; either version 3.0 of the License, or (at your option)
+// any later version.
+// This library 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 Lesser General Public License for more
+// details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this library; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+//------------------------------------------------------------------------------
+
+#include "admin.hh"
+
+namespace ost { namespace gui {
+
+#if defined(__APPLE__)
+
+AdminRights::AdminRights(): auth_(NULL)
+{
+  
+}
+AdminRights::~AdminRights()
+{
+  this->Release();
+}
+
+bool AdminRights::Acquire()
+{
+  // Connect to Authorization Services.
+  if (AuthorizationCreate(NULL, NULL, 0, &auth_)!=noErr) {
+    return false;
+  }
+    
+  static const AuthorizationFlags  kFlags =
+                kAuthorizationFlagInteractionAllowed
+              | kAuthorizationFlagExtendRights;
+  AuthorizationItem   kActionRight = { "", 0, 0, 0 };
+  AuthorizationRights kRights      = { 1, &kActionRight };
+
+  assert(gAuthorization != NULL);
+
+  // Request the application-specific right.
+  return noErr==AuthorizationCopyRights(auth_, &kRights, NULL, kFlags, NULL);
+}
+
+void AdminRights::Release()
+{
+  if (auth_) {
+    AuthorizationFree(auth_, kAuthorizationFlagDestroyRights);
+    auth_=NULL;
+  }
+}
+
+void AdminRights::CreateLink(const String& from, const String& to)
+{
+  static const char* minus_s="-s";
+  // const_casts are gross, but at least that way we keep gcc happy
+  char* const args[]={
+    const_cast<char*>(minus_s),
+    const_cast<char*>(from.c_str()),
+    const_cast<char*>(to.c_str()),
+    NULL
+  };
+  AuthorizationExecuteWithPrivileges(auth_, "/bin/ln", 
+                                     kAuthorizationFlagDefaults,
+                                     args, NULL);
+}
+
+#else
+
+AdminRights::AdminRights()
+{ }
+
+void AdminRights::CreateLink(const String& from, const String& to)
+{
+  
+}
+AdminRights::~AdminRights()
+{ }
+
+bool AdminRights::Acquire()
+{ }
+
+void AdminRights::Release()
+{ }
+#endif
+
+
+}}
+
diff --git a/modules/gui/src/admin.hh b/modules/gui/src/admin.hh
new file mode 100644
index 000000000..99e43e860
--- /dev/null
+++ b/modules/gui/src/admin.hh
@@ -0,0 +1,49 @@
+//------------------------------------------------------------------------------
+// This file is part of the OpenStructure project <www.openstructure.org>
+//
+// Copyright (C) 2008-2010 by the OpenStructure authors
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License as published by the Free
+// Software Foundation; either version 3.0 of the License, or (at your option)
+// any later version.
+// This library 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 Lesser General Public License for more
+// details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this library; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+//------------------------------------------------------------------------------
+
+#ifndef OST_GUI_ADMIN_HH
+#define OST_GUI_ADMIN_HH
+
+#include <ost/gui/module_config.hh>
+
+#if defined(__APPLE__)
+#include <Security/Security.h>
+#endif
+
+namespace ost { namespace gui {
+
+
+class DLLEXPORT_OST_GUI AdminRights {
+public:
+  AdminRights();
+  ~AdminRights();
+  
+  bool Acquire();
+  
+  void CreateLink(const String& from, const String& to);
+  void Release();
+private:  
+#if defined(__APPLE__)
+  AuthorizationRef  auth_;
+#endif
+};
+
+}}
+
+#endif
diff --git a/scripts/init.py b/scripts/init.py
index df9341020..7d79e018f 100644
--- a/scripts/init.py
+++ b/scripts/init.py
@@ -18,7 +18,7 @@ from ost.gui.init_menubar import _InitMenuBar
 from ost.gui.init_spacenav import _InitSpaceNav
 from ost.gui.init_context_menu import _InitContextMenu
 from ost.gui.init_splash import _InitSplash
-
+from ost.gui.dng import termuse
 def _InitRuleBasedBuilder():
   compound_lib_path=os.path.join(ost.GetSharedDataPath(), 'compounds.chemlib')
   if os.path.exists(compound_lib_path):
@@ -56,6 +56,7 @@ def _InitPanels(app):
     return False
   return True
 
+
 def _InitFrontEnd():
   _CheckRestore()
   app=gui.GostyApp.Instance()
@@ -64,6 +65,11 @@ def _InitFrontEnd():
   _InitMenuBar(app)
   if not _InitPanels(app):
     _InitSplash()
+  if sys.platform=='darwin':
+    settings=QtCore.QSettings()    
+    if not settings.value('install/clt', False).toBool():
+      settings.setValue('install/clt', QtCore.QVariant(True))
+      termuse.InstallTerminalPrograms()
   _InitSpaceNav(app)
   _InitContextMenu(app)
   main_area.AddPersistentWidget("3D Scene", "gl_win" , app.gl_win, int(QtCore.Qt.WindowMaximized))
@@ -72,6 +78,7 @@ def _InitFrontEnd():
   for module_name in additional_modules:
     __import__(module_name)
   app.ProcessEvents()
+
   _InitInspector(app)
   
 def _load_files():
-- 
GitLab