Add api to provide tweeting in educational plugins
authorValentina Kiryushkina <valentina.kiryushkina@jetbrains.com>
Thu, 10 Mar 2016 12:36:49 +0000 (15:36 +0300)
committerValentina Kiryushkina <valentina.kiryushkina@jetbrains.com>
Tue, 15 Mar 2016 16:41:52 +0000 (19:41 +0300)
16 files changed:
python/educational-core/educational-core.iml
python/educational-core/student/lib/twitter4j-core-4.0.4.jar [new file with mode: 0644]
python/educational-core/student/resources/META-INF/plugin.xml
python/educational-core/student/src/com/jetbrains/edu/learning/StudyBasePluginConfigurator.java
python/educational-core/student/src/com/jetbrains/edu/learning/StudyPluginConfigurator.java
python/educational-core/student/src/com/jetbrains/edu/learning/actions/StudyAfterCheckAction.java
python/educational-core/student/src/com/jetbrains/edu/learning/checker/StudyCheckTask.java
python/educational-core/student/src/com/jetbrains/edu/learning/twitter/StudyTwitterAction.java [new file with mode: 0644]
python/educational-core/student/src/com/jetbrains/edu/learning/twitter/StudyTwitterUtils.java [new file with mode: 0644]
python/educational-core/student/student.iml
python/educational-python/build/pycharm_edu_build.gant
python/educational-python/educational-python.iml
python/educational-python/student-python/src/com/jetbrains/edu/learning/PyStudyPluginConfigurator.java
python/educational-python/student-python/src/com/jetbrains/edu/learning/settings/PySettingsPanel.form [deleted file]
python/educational-python/student-python/src/com/jetbrains/edu/learning/settings/PySettingsPanel.java [deleted file]
python/educational-python/student-python/src/com/jetbrains/edu/learning/settings/PyStudySettings.java [deleted file]

index 8433b9de516e6a25440c7cff214b80b7f98f8fb8..1df588c98a7373ad24db75aec4f3e83c7300e314 100644 (file)
@@ -8,5 +8,12 @@
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="module" module-name="platform-api" />
+    <orderEntry type="library" name="gson" level="project" />
+    <orderEntry type="module" module-name="core-impl" />
+    <orderEntry type="module" module-name="platform-impl" />
+    <orderEntry type="module" module-name="lang-impl" />
+    <orderEntry type="library" name="http-client" level="project" />
+    <orderEntry type="library" name="twitter4j-core-4.0.4" level="project" />
   </component>
 </module>
\ No newline at end of file
   </component>
 </module>
\ No newline at end of file
diff --git a/python/educational-core/student/lib/twitter4j-core-4.0.4.jar b/python/educational-core/student/lib/twitter4j-core-4.0.4.jar
new file mode 100644 (file)
index 0000000..029abce
Binary files /dev/null and b/python/educational-core/student/lib/twitter4j-core-4.0.4.jar differ
index 3e10b6a2bb2e8b3b19ea75d56b2e4e8eccea0caa..9d5142d93775fd71a698e8fe8b12735b68cbc5bb 100644 (file)
@@ -80,8 +80,6 @@
                              displayName="Educational"/>
     <applicationService serviceInterface="com.jetbrains.edu.learning.stepic.StudySettings"
                         serviceImplementation="com.jetbrains.edu.learning.stepic.StudySettings"/>
                              displayName="Educational"/>
     <applicationService serviceInterface="com.jetbrains.edu.learning.stepic.StudySettings"
                         serviceImplementation="com.jetbrains.edu.learning.stepic.StudySettings"/>
-    <projectService serviceInterface="com.jetbrains.edu.learning.settings.PyStudySettings"
-                    serviceImplementation="com.jetbrains.edu.learning.settings.PyStudySettings"/>
 
     <toolWindow id="Task Description" anchor="right" factoryClass="com.jetbrains.edu.learning.ui.StudyToolWindowFactory" conditionClass="com.jetbrains.edu.learning.ui.StudyCondition"/>
     <toolWindow id="Course Progress" anchor="left" factoryClass="com.jetbrains.edu.learning.ui.StudyProgressToolWindowFactory" conditionClass="com.jetbrains.edu.learning.ui.StudyCondition"/>
 
     <toolWindow id="Task Description" anchor="right" factoryClass="com.jetbrains.edu.learning.ui.StudyToolWindowFactory" conditionClass="com.jetbrains.edu.learning.ui.StudyCondition"/>
     <toolWindow id="Course Progress" anchor="left" factoryClass="com.jetbrains.edu.learning.ui.StudyProgressToolWindowFactory" conditionClass="com.jetbrains.edu.learning.ui.StudyCondition"/>
index e1900e133df5a9aca738e08fb6c4ca476212031e..801410c8d431688e923498266708d85dc2989440 100644 (file)
@@ -6,9 +6,11 @@ import com.intellij.openapi.fileEditor.FileEditorManagerEvent;
 import com.intellij.openapi.fileEditor.FileEditorManagerListener;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.openapi.fileEditor.FileEditorManagerListener;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.vfs.VirtualFile;
+import com.jetbrains.edu.learning.actions.*;
+import com.jetbrains.edu.learning.courseFormat.StudyStatus;
 import com.jetbrains.edu.learning.courseFormat.Task;
 import com.jetbrains.edu.learning.courseFormat.TaskFile;
 import com.jetbrains.edu.learning.courseFormat.Task;
 import com.jetbrains.edu.learning.courseFormat.TaskFile;
-import com.jetbrains.edu.learning.actions.*;
+import com.jetbrains.edu.learning.twitter.StudyTwitterUtils;
 import com.jetbrains.edu.learning.ui.StudyToolWindow;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import com.jetbrains.edu.learning.ui.StudyToolWindow;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -93,4 +95,44 @@ public abstract class StudyBasePluginConfigurator implements StudyPluginConfigur
   public StudyAfterCheckAction[] getAfterCheckActions() {
     return null;
   }
   public StudyAfterCheckAction[] getAfterCheckActions() {
     return null;
   }
+
+  @NotNull
+  @Override
+  public String getConsumerKey(@NotNull Project project) {
+    return "";
+  }
+
+  @NotNull
+  @Override
+  public String getConsumerSecret(@NotNull Project project) {
+    return "";
+  }
+
+  @Override
+  public void storeTwitterTokens(@NotNull Project project, @NotNull String accessToken, @NotNull String tokenSecret) {
+    // do nothing
+  }
+  
+  @NotNull
+  @Override
+  public String getTwitterTokenSecret(@NotNull Project project) {
+    return "";
+  }
+
+  @NotNull
+  @Override
+  public String getTwitterAccessToken(@NotNull Project project) {
+    return "";
+  }
+
+  @Override
+  public boolean askToTweet(@NotNull Project project, Task solvedTask, StudyStatus statusBeforeCheck) {
+    return false;
+  }
+
+  @Nullable
+  @Override
+  public StudyTwitterUtils.TwitterDialogPanel getTweetDialogPanel(@NotNull Task solvedTask) {
+    return null;
+  }
 }
 }
index 4561c2b903aa59b2787e308e35035facddc816d1..4aa1f272acb539cd826e74840c496b168ad9c118 100644 (file)
@@ -5,7 +5,10 @@ import com.intellij.openapi.extensions.ExtensionPointName;
 import com.intellij.openapi.fileEditor.FileEditorManagerListener;
 import com.intellij.openapi.project.Project;
 import com.jetbrains.edu.learning.actions.StudyAfterCheckAction;
 import com.intellij.openapi.fileEditor.FileEditorManagerListener;
 import com.intellij.openapi.project.Project;
 import com.jetbrains.edu.learning.actions.StudyAfterCheckAction;
+import com.jetbrains.edu.learning.courseFormat.StudyStatus;
+import com.jetbrains.edu.learning.courseFormat.Task;
 import com.jetbrains.edu.learning.settings.ModifiableSettingsPanel;
 import com.jetbrains.edu.learning.settings.ModifiableSettingsPanel;
+import com.jetbrains.edu.learning.twitter.StudyTwitterUtils;
 import com.jetbrains.edu.learning.ui.StudyToolWindow;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 import com.jetbrains.edu.learning.ui.StudyToolWindow;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -18,7 +21,6 @@ public interface StudyPluginConfigurator {
 
   /**
    * Provide action group that should be placed on the tool window toolbar.
 
   /**
    * Provide action group that should be placed on the tool window toolbar.
-   * @param project
    * @return
    */
   @NotNull
    * @return
    */
   @NotNull
@@ -26,7 +28,6 @@ public interface StudyPluginConfigurator {
 
   /**
    * Provide panels, that could be added to Task tool window.
 
   /**
    * Provide panels, that could be added to Task tool window.
-   * @param project
    * @return Map from panel id, i.e. "Task description", to panel itself.
    */
   @NotNull
    * @return Map from panel id, i.e. "Task description", to panel itself.
    */
   @NotNull
@@ -45,9 +46,50 @@ public interface StudyPluginConfigurator {
   StudyAfterCheckAction[] getAfterCheckActions();
   
   @NotNull String getLanguageScriptUrl();
   StudyAfterCheckAction[] getAfterCheckActions();
   
   @NotNull String getLanguageScriptUrl();
-  
-  boolean accept(@NotNull final Project project);
 
   @Nullable
   ModifiableSettingsPanel getSettingsPanel();
 
   @Nullable
   ModifiableSettingsPanel getSettingsPanel();
+
+  /**
+   * To implement tweeting you should register you app in twitter. For registered application twitter provide
+   * consumer key and consumer secret, that are used for authorize by OAuth.
+   * @return consumer key for current educational plugin
+   */
+  @NotNull String getConsumerKey(@NotNull final Project project);
+
+  /**
+   * To implement tweeting you should register you app in twitter. For registered application twitter provide
+   * consumer key and consumer secret, that are used for authorize by OAuth.
+   * @return consumer secret for current educational plugin
+   */
+  @NotNull String getConsumerSecret(@NotNull final Project project);
+
+  /**
+   * The plugin implemented tweeting should define policy when user will be asked to tweet.
+   *@param statusBeforeCheck @return 
+   */
+  boolean askToTweet(@NotNull final Project project, Task solvedTask, StudyStatus statusBeforeCheck);
+  
+  /**
+   * Stores access token and token secret, obtained by authorizing PyCharm.
+   */
+  void storeTwitterTokens(@NotNull final Project project, @NotNull final String accessToken, @NotNull final String tokenSecret);
+
+  /**
+   * @return stored access token
+   */
+  @NotNull String getTwitterAccessToken(@NotNull Project project);
+
+  /**
+   * @return stored token secret
+   */
+  @NotNull String getTwitterTokenSecret(@NotNull Project project);
+
+  /**
+   * @return panel that will be shown to user in ask to tweet dialog. 
+   */
+  @Nullable
+  StudyTwitterUtils.TwitterDialogPanel getTweetDialogPanel(@NotNull Task solvedTask);
+  
+  boolean accept(@NotNull final Project project);
 }
 }
index b85df4256cf86dde8bd93f29509e29913add2878..ecd467777219ec7d9f3f944eba34179d5a62afbf 100644 (file)
@@ -1,8 +1,8 @@
 package com.jetbrains.edu.learning.actions;
 
 import com.intellij.openapi.project.Project;
 package com.jetbrains.edu.learning.actions;
 
 import com.intellij.openapi.project.Project;
-import com.jetbrains.edu.courseFormat.StudyStatus;
-import com.jetbrains.edu.courseFormat.Task;
+import com.jetbrains.edu.learning.courseFormat.StudyStatus;
+import com.jetbrains.edu.learning.courseFormat.Task;
 import org.jetbrains.annotations.NotNull;
 
 public abstract class StudyAfterCheckAction {
 import org.jetbrains.annotations.NotNull;
 
 public abstract class StudyAfterCheckAction {
index bdeb215a89f1fbc892d78bed6a5fe8d58393ebfe..9a372fe39fc1ab329315188f4e5f66c41d156c7d 100644 (file)
@@ -11,15 +11,15 @@ import com.intellij.openapi.ui.MessageType;
 import com.intellij.openapi.util.Ref;
 import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.openapi.util.Ref;
 import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.openapi.vfs.VirtualFile;
-import com.jetbrains.edu.learning.core.EduUtils;
-import com.jetbrains.edu.learning.courseFormat.StudyStatus;
-import com.jetbrains.edu.learning.courseFormat.Task;
 import com.jetbrains.edu.learning.StudyPluginConfigurator;
 import com.jetbrains.edu.learning.StudyState;
 import com.jetbrains.edu.learning.StudyTaskManager;
 import com.jetbrains.edu.learning.StudyUtils;
 import com.jetbrains.edu.learning.actions.StudyAfterCheckAction;
 import com.jetbrains.edu.learning.StudyPluginConfigurator;
 import com.jetbrains.edu.learning.StudyState;
 import com.jetbrains.edu.learning.StudyTaskManager;
 import com.jetbrains.edu.learning.StudyUtils;
 import com.jetbrains.edu.learning.actions.StudyAfterCheckAction;
-import com.jetbrains.edu.learning.EduStepicConnector;
+import com.jetbrains.edu.learning.core.EduUtils;
+import com.jetbrains.edu.learning.courseFormat.StudyStatus;
+import com.jetbrains.edu.learning.courseFormat.Task;
+import com.jetbrains.edu.learning.stepic.EduStepicConnector;
 import com.jetbrains.edu.learning.stepic.StudySettings;
 import org.jetbrains.annotations.NotNull;
 
 import com.jetbrains.edu.learning.stepic.StudySettings;
 import org.jetbrains.annotations.NotNull;
 
diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/twitter/StudyTwitterAction.java b/python/educational-core/student/src/com/jetbrains/edu/learning/twitter/StudyTwitterAction.java
new file mode 100644 (file)
index 0000000..dd74afa
--- /dev/null
@@ -0,0 +1,49 @@
+package com.jetbrains.edu.learning.twitter;
+
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.jetbrains.edu.learning.StudyPluginConfigurator;
+import com.jetbrains.edu.learning.StudyUtils;
+import com.jetbrains.edu.learning.actions.StudyAfterCheckAction;
+import com.jetbrains.edu.learning.courseFormat.StudyStatus;
+import com.jetbrains.edu.learning.courseFormat.Task;
+import org.jetbrains.annotations.NotNull;
+import twitter4j.Twitter;
+import twitter4j.TwitterException;
+
+/**
+ * Action that provide tweeting functionality to plugin.
+ * Is performed for every solved task and configured by StudyPluginConfigurator instance.
+ * 
+ * In order to provide tweeting functionality in your plugin you should override twitter 
+ * methods in StudyPluginConfigurator instance of your plugin.
+ */
+public class StudyTwitterAction extends StudyAfterCheckAction {
+  Logger LOG = Logger.getInstance(StudyTwitterAction.class);
+  @Override
+  public void run(@NotNull Project project, @NotNull Task solvedTask, StudyStatus statusBeforeCheck) {
+    try {
+      StudyPluginConfigurator configurator = StudyUtils.getConfigurator(project);
+      if (configurator == null) {
+        LOG.warn("Plugin configurator not found");
+        return;
+      }
+      
+      if (configurator.askToTweet(project, solvedTask, statusBeforeCheck)) {
+        boolean isAuthorized = !configurator.getTwitterAccessToken(project).isEmpty();
+        Twitter twitter = StudyTwitterUtils.getTwitter(configurator.getConsumerKey(project), configurator.getConsumerSecret(project));
+        StudyTwitterUtils.configureTwitter(twitter, project, isAuthorized);
+        StudyTwitterUtils.TwitterDialogPanel panel = configurator.getTweetDialogPanel(solvedTask);
+        if (panel != null) {
+          StudyTwitterUtils.showPostTweetDialogAndPostTweet(twitter, panel);
+        }
+        else {
+          LOG.warn("Plugin didn't provide twitter panel");          
+        }
+      } 
+    }
+    catch (TwitterException e) {
+      LOG.warn(e.getMessage());
+    }
+  }
+}
diff --git a/python/educational-core/student/src/com/jetbrains/edu/learning/twitter/StudyTwitterUtils.java b/python/educational-core/student/src/com/jetbrains/edu/learning/twitter/StudyTwitterUtils.java
new file mode 100644 (file)
index 0000000..3fdab6b
--- /dev/null
@@ -0,0 +1,212 @@
+package com.jetbrains.edu.learning.twitter;
+
+import com.intellij.ide.BrowserUtil;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.DialogBuilder;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.ui.DocumentAdapter;
+import com.intellij.ui.components.JBScrollPane;
+import com.jetbrains.edu.learning.StudyPluginConfigurator;
+import com.jetbrains.edu.learning.StudyUtils;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import twitter4j.StatusUpdate;
+import twitter4j.Twitter;
+import twitter4j.TwitterException;
+import twitter4j.TwitterFactory;
+import twitter4j.auth.AccessToken;
+import twitter4j.auth.RequestToken;
+import twitter4j.conf.ConfigurationBuilder;
+
+import javax.swing.*;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+
+public class StudyTwitterUtils {
+  private static final Logger LOG = Logger.getInstance(StudyTwitterUtils.class);
+
+  /**
+   * Configure twitter instance: authorize if needed or set access token and token secret provided by configurator.
+   * @param twitter
+   * @param project
+   * @param isAuthorized
+   * @throws TwitterException
+   */
+  public static void configureTwitter(@NotNull final Twitter twitter, @NotNull final Project project,
+                                         final boolean isAuthorized) throws TwitterException {
+    if (!isAuthorized) {
+      authorize(project, twitter);
+    }
+    else {
+      StudyPluginConfigurator configurator = StudyUtils.getConfigurator(project);
+      if (configurator != null) {
+        getTwitterForAuthorizedApp(twitter, configurator.getTwitterAccessToken(project), configurator.getTwitterTokenSecret(project));
+      }
+    }
+  }
+
+  /**
+   * Set consumer key and secret. 
+   * @param consumerKey
+   * @param consumerSecret
+   * @return
+   */
+  @NotNull
+  public static Twitter getTwitter(@NotNull final String consumerKey, @NotNull final String consumerSecret) {
+    ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
+    configurationBuilder.setOAuthConsumerKey(consumerKey);
+    configurationBuilder.setOAuthConsumerSecret(consumerSecret);
+    return new TwitterFactory(configurationBuilder.build()).getInstance();
+  }
+
+  /**
+   * Set access token and token secret in Twitter instance
+   * @param twitter
+   * @param accessToken
+   * @param tokenSecret
+   */
+  private static void getTwitterForAuthorizedApp(Twitter twitter, @NotNull String accessToken,
+                                                @NotNull String tokenSecret) {
+    AccessToken token = new AccessToken(accessToken, tokenSecret);
+    twitter.setOAuthAccessToken(token);
+  }
+
+  /**
+   * Authorize user and save tokens by StudyPluginConfigurator#storeTwitterTokens
+   * @param project
+   * @param twitter
+   * @throws TwitterException
+   */
+  public static void authorize(@NotNull final Project project, @NotNull final Twitter twitter) throws TwitterException {
+    RequestToken requestToken = twitter.getOAuthRequestToken();
+    BrowserUtil.browse(requestToken.getAuthorizationURL());
+
+    ApplicationManager.getApplication().invokeLater(() -> {
+      String pin = Messages.showInputDialog("Twitter PIN:", "Twitter Authorization", null, "", null);
+      try {
+        AccessToken token;
+        if (pin != null && pin.length() > 0) {
+          token = twitter.getOAuthAccessToken(requestToken, pin);
+        }
+        else {
+          token = twitter.getOAuthAccessToken();
+        }
+        StudyPluginConfigurator configurator = StudyUtils.getConfigurator(project);
+        if (configurator != null) {
+          configurator.storeTwitterTokens(project, token.getToken(), token.getTokenSecret());
+        }
+        else {
+          LOG.warn("Plugin configurator not found");
+        }
+      }
+      catch (TwitterException e) {
+        if (401 == e.getStatusCode()) {
+          LOG.warn("Unable to get the access token.");
+        }
+        else {
+          LOG.warn(e.getMessage());
+        }
+      }
+    });
+  }
+
+  /**
+   * Show twitter dialog, asking user to tweet about his achievements. Post tweet with provided by panel
+   * media and text. 
+   * As a result of succeeded tweet twitter website is opened in default browser.
+   * @param twitter 
+   * @param twitterDialogPanel 
+   */
+  public static void showPostTweetDialogAndPostTweet(@NotNull Twitter twitter, @NotNull final TwitterDialogPanel twitterDialogPanel) {
+    ApplicationManager.getApplication().invokeLater(() -> {
+      DialogBuilder builder = new DialogBuilder();
+      twitterDialogPanel.addTextFieldVerifier(createTextFieldLengthDocumentListener(builder, twitterDialogPanel));
+      builder.title("Twitter");
+      builder.addOkAction().setText("Tweet");
+      builder.addCancelAction();
+      builder.setCenterPanel(new JBScrollPane(twitterDialogPanel));
+      builder.resizable(true);
+      if (builder.showAndGet()) {
+        StatusUpdate update = new StatusUpdate(twitterDialogPanel.getMessage());
+        try {
+          InputStream inputStream = twitterDialogPanel.getMediaSource();
+          if (inputStream != null) {
+            File imageFile = FileUtil.createTempFile("twitter_media", "gif");
+            
+            FileUtil.copy(inputStream, new FileOutputStream(imageFile));
+            update.media(imageFile);
+          }
+          twitter.updateStatus(update);
+          BrowserUtil.browse("https://twitter.com/");
+        }
+        catch (IOException | TwitterException e) {
+          LOG.warn(e.getMessage());
+          Messages.showErrorDialog("Status wasn't updated. Please, check internet connection and try again", "Twitter");
+        }
+      }
+    });
+  }
+
+  /**
+   * Listener updates label indicating remaining symbols number like in twitter.
+   * @param builder
+   * @param panel
+   * @return
+   */
+  public static DocumentListener createTextFieldLengthDocumentListener(@NotNull DialogBuilder builder, @NotNull final TwitterDialogPanel panel) {
+    return new DocumentAdapter() {
+      @Override
+      protected void textChanged(DocumentEvent e) {
+        int length = e.getDocument().getLength();
+        if (length > 140 || length == 0) {
+          builder.setOkActionEnabled(false);
+          panel.getRemainSymbolsLabel().setText("<html><font color='red'>" + String.valueOf(140 - length) + "</font></html>");
+        }
+        else {
+          builder.setOkActionEnabled(true);
+          panel.getRemainSymbolsLabel().setText(String.valueOf(140 - length));
+        }
+        
+      }
+    };
+  }
+
+  /**
+   * Class provides structure for twitter dialog panel
+   */
+  public abstract static class TwitterDialogPanel extends JPanel {
+
+    /**
+     * Provides tweet text
+     * @return 
+     */
+    @NotNull public abstract String getMessage();
+
+    /**
+     * 
+     * @return Input stream of media should be posted or null if there's nothing to post 
+     */
+    @Nullable public abstract InputStream getMediaSource();
+
+    /**
+     * 
+     * @return label that will be used to show remained symbol number
+     */
+    @NotNull public abstract JLabel getRemainSymbolsLabel();
+
+    /**
+     * Api to add document listener to field containing tweet text
+     * @param documentListener
+     */
+    public abstract void addTextFieldVerifier(@NotNull final DocumentListener documentListener);
+    
+  }
+}
index c4f9d2bccdb907e53fba38e04be3de9f728c5166..0ebe6c7ad38bc07f6c4d4bfc6e529c0fe74ba13a 100644 (file)
@@ -11,6 +11,8 @@
     <orderEntry type="sourceFolder" forTests="false" />
     <orderEntry type="module" module-name="lang-impl" />
     <orderEntry type="library" name="gson" level="project" />
     <orderEntry type="sourceFolder" forTests="false" />
     <orderEntry type="module" module-name="lang-impl" />
     <orderEntry type="library" name="gson" level="project" />
+    <orderEntry type="module" module-name="educational-core" />
+    <orderEntry type="library" name="twitter4j-core-4.0.4" level="project" />
     <orderEntry type="library" name="http-client" level="project" />
   </component>
 </module>
\ No newline at end of file
     <orderEntry type="library" name="http-client" level="project" />
   </component>
 </module>
\ No newline at end of file
index 25ca87b39d959924164aa62ea13b1965f0d39522..d78efb76507b813f973d4547a5cf4be357699bc8 100644 (file)
@@ -224,7 +224,9 @@ public layoutEducational(String classesPath, Set usedJars) {
 
 private layoutPlugins(layouts) {
   dir("plugins") {
 
 private layoutPlugins(layouts) {
   dir("plugins") {
-    layouts.layoutPlugin("student")
+    layouts.layoutPlugin("student") {
+      fileset(dir: "$pythonCommunityHome/educational-core/student/lib")
+    }
     layouts.layoutPlugin("student-python") {
       dir("courses") {
         fileset(dir: "$pythonEduHome/student-python/resources/courses")
     layouts.layoutPlugin("student-python") {
       dir("courses") {
         fileset(dir: "$pythonEduHome/student-python/resources/courses")
index 5832d9d1b31af8c167c99d6240d2e77e641d1f8f..20278fc43f443f997863bbf2398d0a171d7bb4a4 100644 (file)
@@ -24,5 +24,6 @@
     <orderEntry type="module" module-name="xml-psi-impl" />
     <orderEntry type="module" module-name="python-community-ide-resources" />
     <orderEntry type="module" module-name="python-community" />
     <orderEntry type="module" module-name="xml-psi-impl" />
     <orderEntry type="module" module-name="python-community-ide-resources" />
     <orderEntry type="module" module-name="python-community" />
+    <orderEntry type="library" name="twitter4j-core-4.0.4" level="project" />
   </component>
 </module>
\ No newline at end of file
   </component>
 </module>
\ No newline at end of file
index e5e6dbec1dafce27064308760904361f1c96f8a9..88688a8ae8a5339e06a3bdb9f493da25eab8712f 100644 (file)
@@ -3,10 +3,7 @@ package com.jetbrains.edu.learning;
 import com.intellij.openapi.actionSystem.DefaultActionGroup;
 import com.intellij.openapi.project.Project;
 import com.jetbrains.edu.learning.courseFormat.Course;
 import com.intellij.openapi.actionSystem.DefaultActionGroup;
 import com.intellij.openapi.project.Project;
 import com.jetbrains.edu.learning.courseFormat.Course;
-import com.jetbrains.edu.learning.actions.PyTwitterAction;
-import com.jetbrains.edu.learning.actions.StudyAfterCheckAction;
 import com.jetbrains.edu.learning.settings.ModifiableSettingsPanel;
 import com.jetbrains.edu.learning.settings.ModifiableSettingsPanel;
-import com.jetbrains.edu.learning.settings.PySettingsPanel;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -34,12 +31,6 @@ public class PyStudyPluginConfigurator extends StudyBasePluginConfigurator {
     return getClass().getResource("/python.js").toExternalForm();
   }
 
     return getClass().getResource("/python.js").toExternalForm();
   }
 
-  @Nullable
-  @Override
-  public StudyAfterCheckAction[] getAfterCheckActions() {
-    return new StudyAfterCheckAction[]{new PyTwitterAction()};
-  }
-
   @Override
   public boolean accept(@NotNull Project project) {
     StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
   @Override
   public boolean accept(@NotNull Project project) {
     StudyTaskManager taskManager = StudyTaskManager.getInstance(project);
@@ -51,6 +42,6 @@ public class PyStudyPluginConfigurator extends StudyBasePluginConfigurator {
   @Nullable
   @Override
   public ModifiableSettingsPanel getSettingsPanel() {
   @Nullable
   @Override
   public ModifiableSettingsPanel getSettingsPanel() {
-    return new PySettingsPanel();
+    return null;
   }
 }
   }
 }
diff --git a/python/educational-python/student-python/src/com/jetbrains/edu/learning/settings/PySettingsPanel.form b/python/educational-python/student-python/src/com/jetbrains/edu/learning/settings/PySettingsPanel.form
deleted file mode 100644 (file)
index a51a16c..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.jetbrains.edu.learning.settings.PySettingsPanel">
-  <grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="3" column-count="4" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
-    <margin top="0" left="0" bottom="0" right="0"/>
-    <constraints>
-      <xy x="20" y="20" width="500" height="400"/>
-    </constraints>
-    <properties/>
-    <border type="none"/>
-    <children>
-      <vspacer id="d7660">
-        <constraints>
-          <grid row="2" column="0" row-span="1" col-span="4" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
-        </constraints>
-      </vspacer>
-      <component id="62464" class="com.intellij.ui.components.JBLabel">
-        <constraints>
-          <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
-        </constraints>
-        <properties>
-          <text value="Ask to tweet after lesson completion"/>
-        </properties>
-      </component>
-      <component id="ba733" class="com.intellij.ui.TitledSeparator">
-        <constraints>
-          <grid row="0" column="0" row-span="1" col-span="4" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
-        </constraints>
-        <properties>
-          <text value="Python"/>
-          <titleFont size="14" style="1"/>
-        </properties>
-        <clientProperties>
-          <BorderFactoryClass class="java.lang.String" value=""/>
-          <html.disable class="java.lang.Boolean" value="false"/>
-        </clientProperties>
-      </component>
-      <component id="1740e" class="com.intellij.ui.components.JBCheckBox" binding="myAskToTweetCheckBox">
-        <constraints>
-          <grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
-        </constraints>
-        <properties>
-          <selected value="true"/>
-        </properties>
-      </component>
-      <hspacer id="d7ecb">
-        <constraints>
-          <grid row="1" column="2" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
-        </constraints>
-      </hspacer>
-    </children>
-  </grid>
-</form>
diff --git a/python/educational-python/student-python/src/com/jetbrains/edu/learning/settings/PySettingsPanel.java b/python/educational-python/student-python/src/com/jetbrains/edu/learning/settings/PySettingsPanel.java
deleted file mode 100644 (file)
index 240b567..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-package com.jetbrains.edu.learning.settings;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.project.ProjectUtil;
-import com.intellij.ui.components.JBCheckBox;
-import com.intellij.util.ui.UIUtil;
-import org.jetbrains.annotations.NotNull;
-
-import javax.swing.*;
-
-
-public class PySettingsPanel implements ModifiableSettingsPanel{
-  private JBCheckBox myAskToTweetCheckBox;
-  private JPanel myPanel;
-  private boolean myIsModified = false;
-
-  public PySettingsPanel() {
-    myAskToTweetCheckBox.addActionListener(e -> myIsModified = true);
-    myAskToTweetCheckBox.setSelected(PyStudySettings.getInstance(ProjectUtil.guessCurrentProject(myPanel)).askToTweet());
-    myPanel.setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, UIUtil.getBoundsColor()));
-  }
-
-  @Override
-  public void apply() {
-    Project project = ProjectUtil.guessCurrentProject(myPanel);
-    PyStudySettings.getInstance(project).setAskToTweet(myAskToTweetCheckBox.isSelected());
-  }
-
-  @Override
-  public void reset() {
-    Project project = ProjectUtil.guessCurrentProject(myPanel);
-    PyStudySettings.getInstance(project).setAskToTweet(true);
-  }
-
-  @Override
-  public void resetCredentialsModification() {
-    myIsModified = false;
-  }
-
-  @Override
-  public boolean isModified() {
-    return myIsModified;
-  }
-
-  @NotNull
-  public JPanel getPanel() {
-    return myPanel;
-  }
-}
diff --git a/python/educational-python/student-python/src/com/jetbrains/edu/learning/settings/PyStudySettings.java b/python/educational-python/student-python/src/com/jetbrains/edu/learning/settings/PyStudySettings.java
deleted file mode 100644 (file)
index b2bc22b..0000000
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.jetbrains.edu.learning.settings;
-
-import com.intellij.openapi.components.PersistentStateComponent;
-import com.intellij.openapi.components.ServiceManager;
-import com.intellij.openapi.components.State;
-import com.intellij.openapi.components.Storage;
-import com.intellij.openapi.project.Project;
-import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-
-@SuppressWarnings("MethodMayBeStatic")
-@State(name = "PyStudySettings", storages = @Storage("py_study_settings.xml"))
-public class PyStudySettings implements PersistentStateComponent<PyStudySettings.State> {
-
-  private State myState = new State();
-
-
-  public static class State {
-    public boolean askToTweet = true;
-  }
-
-  public static PyStudySettings getInstance(@NotNull final Project project) {
-    return ServiceManager.getService(project, PyStudySettings.class);
-  }
-  @Nullable
-  @Override
-  public State getState() {
-    return myState;
-  }
-
-  @Override
-  public void loadState(State state) {
-    myState = state;
-  }
-  
-  public boolean askToTweet() {
-    return myState.askToTweet;
-  }
-  
-  public void setAskToTweet(final boolean askToTweet) {
-    myState.askToTweet = askToTweet;
-  }
-}
\ No newline at end of file