//The MIT License // //Copyright (c) 2009 nodchip // //Permission is hereby granted, free of charge, to any person obtaining a copy //of this software and associated documentation files (the "Software"), to deal //in the Software without restriction, including without limitation the rights //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell //copies of the Software, and to permit persons to whom the Software is //furnished to do so, subject to the following conditions: // //The above copyright notice and this permission notice shall be included in //all copies or substantial portions of the Software. // //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN //THE SOFTWARE. package tv.dyndns.kishibe.qmaclone.client.creation; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import tv.dyndns.kishibe.qmaclone.client.Service; import tv.dyndns.kishibe.qmaclone.client.UserData; import tv.dyndns.kishibe.qmaclone.client.bbs.PanelBbs; import tv.dyndns.kishibe.qmaclone.client.creation.ChangeHistoryView.ChangeHistoryPresenter; import tv.dyndns.kishibe.qmaclone.client.creation.validater.Evaluation; import tv.dyndns.kishibe.qmaclone.client.game.ProblemGenre; import tv.dyndns.kishibe.qmaclone.client.game.SessionData; import tv.dyndns.kishibe.qmaclone.client.game.WidgetTimeProgressBar; import tv.dyndns.kishibe.qmaclone.client.game.panel.QuestionPanel; import tv.dyndns.kishibe.qmaclone.client.game.panel.QuestionPanelFactory; import tv.dyndns.kishibe.qmaclone.client.packet.PacketProblem; import tv.dyndns.kishibe.qmaclone.client.packet.PacketProblemCreationLog; import tv.dyndns.kishibe.qmaclone.client.packet.PacketWrongAnswer; import tv.dyndns.kishibe.qmaclone.client.report.ProblemReportUi; import tv.dyndns.kishibe.qmaclone.client.util.StringUtils; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.RepeatingCommand; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.safehtml.shared.SafeHtmlBuilder; import com.google.gwt.safehtml.shared.SafeHtmlUtils; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.uibinder.client.UiHandler; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.Button; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.DialogBox; import com.google.gwt.user.client.ui.FocusWidget; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.HTMLPanel; import com.google.gwt.user.client.ui.Label; import com.google.gwt.user.client.ui.SimplePanel; import com.google.gwt.user.client.ui.TextBox; import com.google.gwt.user.client.ui.VerticalPanel; import com.google.gwt.user.client.ui.Widget; public class CreationUi extends Composite implements ChangeHistoryPresenter { private static final Logger logger = Logger.getLogger(CreationUi.class.getName()); private static final int MAX_SIMILER_PROBLEMS_PER_PAGE = 10; private static final CreationUiUiBinder uiBinder = GWT.create(CreationUiUiBinder.class); @VisibleForTesting static final String MESSAGE_UPDATE_NOTE = "問題ノートを更新してください"; // TODO(nodchip): テストが落ちる原因を特定して削除する @VisibleForTesting static boolean SUPPRESS_GET_SIMILAR_PROBLEM_FOR_TESTING = false; private static final int MIN_NUMBER_OF_PROBLEM_TO_SHOW_PROBLEM_FORM = 30; interface CreationUiUiBinder extends UiBinder<Widget, CreationUi> { } @UiField HTMLPanel htmlPanelSorry; @UiField HTMLPanel htmlRequireGooglePlusLogin; @UiField HTMLPanel htmlPanelMain; @UiField HTMLPanel htmlPanelDone; @UiField Button buttonNewProblem; @UiField Button buttonMoveToVerification; @UiField Button buttonSendProblem; @UiField SimplePanel panelSimilar; @UiField HTMLPanel htmlPanelWrongAnswer; @UiField SimplePanel panelWrongAnswer; @UiField HTMLPanel htmlPanelBbs; @UiField SimplePanel panelBbs; @UiField SimplePanel panelSample; @UiField TextBox textBoxGetProblem; @UiField Button buttonGetProblem; @UiField Button buttonCopyProblem; @UiField SimplePanel panelProblemForm; @UiField VerticalPanel panelWarning; @UiField HTML htmlTypeCaution; @UiField SimplePanel panelChangeHistory; @UiField Label labelProblemId; @UiField Button buttonNextProblem; @VisibleForTesting WidgetProblemForm widgetProblemForm; private boolean sendingProblem = false; private final RepeatingCommand commandCheckProblem = new RepeatingCommand() { @Override public boolean execute() { boolean enabled = validateProblem(); buttonMoveToVerification.setEnabled(enabled && !sendingProblem); buttonSendProblem.setEnabled(enabled && !sendingProblem); return isAttached(); } }; private boolean copyProblem; private final RepeatingCommand commandCheckProblemId = new RepeatingCommand() { @Override public boolean execute() { boolean enabled = checkProblemId(); buttonCopyProblem.setEnabled(enabled); buttonGetProblem.setEnabled(enabled); return isAttached(); } }; private final WrongAnswerPresenter wrongAnswerPresenter; public CreationUi(WrongAnswerPresenter wrongAnswerPresenter) { this.wrongAnswerPresenter = Preconditions.checkNotNull(wrongAnswerPresenter); initWidget(uiBinder.createAndBindUi(this)); reset(); } public void reset() { buttonSendProblem.setVisible(false); htmlPanelSorry.setVisible(false); htmlRequireGooglePlusLogin.setVisible(false); htmlPanelMain.setVisible(false); htmlPanelDone.setVisible(false); htmlPanelWrongAnswer.setVisible(false); htmlPanelBbs.setVisible(false); // TODO(nodchip): 問題作成時にGoogle+ログインを強制する if (UserData.get().getPlayCount() < MIN_NUMBER_OF_PROBLEM_TO_SHOW_PROBLEM_FORM) { htmlPanelSorry.setVisible(true); // } else if (Strings.isNullOrEmpty(UserData.get().getGooglePlusId())) { // htmlRequireGooglePlusLogin.setVisible(true); } else { htmlPanelMain.setVisible(true); } panelSimilar.clear(); panelWrongAnswer.clear(); panelSample.clear(); panelWarning.clear(); panelChangeHistory.clear(); widgetProblemForm = new WidgetProblemForm(this); panelProblemForm.setWidget(widgetProblemForm); textBoxGetProblem.setText(null); // previousProblemNote = null; } /** * 出題形式の説明文を設定する * * @param text * 説明文 */ public void setTypeDescription(String text) { if (Strings.isNullOrEmpty(text)) { htmlTypeCaution.setHTML(""); return; } htmlTypeCaution.setHTML(new SafeHtmlBuilder().appendEscapedLines(text).toSafeHtml()); } /** * 入力されている問題の形式が正しいかどうか調べる * * @return 正しいならtrue */ @VisibleForTesting boolean validateProblem() { clearWarnings(); if (widgetProblemForm == null) { return false; } PacketProblem problem = widgetProblemForm.getProblem(); Evaluation eval = problem.type.validate(problem); // 問題ノートの更新 // BugTrack-QMAClone/589 - QMAClone wiki // http://kishibe.dyndns.tv/qmaclone/wiki/wiki.cgi?page=BugTrack%2DQMAClone%2F589 // if (previousProblemNote != null && previousProblemNote.equals(problem.note)) { // eval.warn.add(MESSAGE_UPDATE_NOTE); // } for (String warning : eval.warn) { addWarnings(warning); } for (String info : eval.info) { addInfo(info); } return !eval.hasWarning(); } private void setProblemSample(PacketProblem problem) { problem.prepareShuffledAnswersAndChoices(); WidgetTimeProgressBar widgetTimeProgressBar = new WidgetTimeProgressBar(); QuestionPanel panelQuestion = QuestionPanelFactory.create(problem, widgetTimeProgressBar, new SessionData(-1, -1, false, false, false)); panelQuestion.enableInput(false); panelQuestion.showCorrectRatioAndCreator(); panelSample.setWidget(panelQuestion); } private void getSimilarProblems(PacketProblem problem) { if (SUPPRESS_GET_SIMILAR_PROBLEM_FOR_TESTING) { return; } Service.Util.getInstance().searchSimilarProblem(problem, callbackSearchSimilarProblem); } private final AsyncCallback<List<PacketProblem>> callbackSearchSimilarProblem = new AsyncCallback<List<PacketProblem>>() { public void onSuccess(List<PacketProblem> result) { panelSimilar .setWidget(new ProblemReportUi(result, true, true, MAX_SIMILER_PROBLEMS_PER_PAGE)); } public void onFailure(Throwable caught) { logger.log(Level.WARNING, "類似問題の検索に失敗しました", caught); } }; public void getWrongAnswers(int problemID) { Service.Util.getInstance().getWrongAnswers(problemID, callbackGetWrongAnswers); } @VisibleForTesting final AsyncCallback<List<PacketWrongAnswer>> callbackGetWrongAnswers = new AsyncCallback<List<PacketWrongAnswer>>() { public void onSuccess(List<PacketWrongAnswer> result) { panelWrongAnswer.setWidget(wrongAnswerPresenter.asWidget()); wrongAnswerPresenter.setWrongAnswers(result, widgetProblemForm.getProblem()); } public void onFailure(Throwable caught) { logger.log(Level.WARNING, "誤回答の取得に失敗しました", caught); } }; private final AsyncCallback<Integer> callbackUploadProblem = new AsyncCallback<Integer>() { public void onSuccess(Integer result) { int userCode = UserData.get().getUserCode(); htmlPanelMain.setVisible(false); htmlPanelDone.setVisible(true); labelProblemId.setText(Integer.toString(result)); if (UserData.get().isRegisterCreatedProblem()) { Service.Util.getInstance().addProblemIdsToReport(userCode, ImmutableList.of(result), callbackAddProblemIdsToReport); } sendingProblem = false; setEnable(true); // 回答数リセット PacketProblem problem = widgetProblemForm.getProblem(); if (problem.needsResetAnswerCount) { Service.Util.getInstance().resetProblemCorrectCounter(UserData.get().getUserCode(), result, callbackResetProblemCorrectCounter); } } public void onFailure(Throwable caught) { sendingProblem = false; setEnable(true); logger.log(Level.WARNING, "問題の送信中にエラーが発生しました", caught); } }; private final AsyncCallback<Void> callbackAddProblemIdsToReport = new AsyncCallback<Void>() { @Override public void onSuccess(Void result) { } @Override public void onFailure(Throwable caught) { logger.log(Level.WARNING, "問題の登録に失敗しました", caught); } }; private void setEnable(boolean enabled) { FocusWidget[] widgets = { buttonNewProblem, buttonMoveToVerification, buttonSendProblem, textBoxGetProblem, buttonGetProblem, buttonCopyProblem, buttonNextProblem }; for (FocusWidget widget : widgets) { widget.setEnabled(enabled); } widgetProblemForm.setEnable(enabled); } private void getProblemFromServer(boolean copy) { // TODO(nodchip): テストを書く copyProblem = copy; int problemId; try { problemId = Integer.parseInt(StringUtils.toHalfWidth(textBoxGetProblem.getText())); } catch (NumberFormatException e) { logger.log(Level.WARNING, "入力された問題番号を数字として解釈できませんでした", e); return; } setEnable(false); Service.Util.getInstance().getProblemList(ImmutableList.of(problemId), callbackGetProblemList); if (copy) { panelChangeHistory.clear(); panelBbs.clear(); htmlPanelWrongAnswer.setVisible(false); htmlPanelBbs.setVisible(false); } else { panelChangeHistory.setWidget(new ChangeHistoryViewImpl(this)); Service.Util.getInstance().getProblemCreationLog(problemId, callbackGetProblemCreationLog); panelBbs.setWidget(new PanelBbs(problemId)); htmlPanelWrongAnswer.setVisible(true); htmlPanelBbs.setVisible(true); } } private final AsyncCallback<List<PacketProblem>> callbackGetProblemList = new AsyncCallback<List<PacketProblem>>() { public void onSuccess(List<PacketProblem> result) { String message = "無効な問題番号が指定されました"; if (result == null || result.isEmpty()) { logger.log(Level.WARNING, message); return; } PacketProblem problem = result.get(0); if (problem == null) { logger.log(Level.WARNING, message); return; } if (copyProblem) { problem = problem.cloneForCopyingProblem(); } widgetProblemForm.setProblem(problem); if (copyProblem) { panelSimilar.clear(); panelWrongAnswer.clear(); panelSample.clear(); panelChangeHistory.clear(); // previousProblemNote = null; } else { setProblemSample(problem); getWrongAnswers(problem.id); panelSimilar.clear(); getSimilarProblems(problem); // previousProblemNote = problem.note; } setEnable(true); } public void onFailure(Throwable caught) { logger.log(Level.WARNING, "問題の取得中にエラーが発生しました", caught); setEnable(true); } }; private final AsyncCallback<List<PacketProblemCreationLog>> callbackGetProblemCreationLog = new AsyncCallback<List<PacketProblemCreationLog>>() { @Override public void onSuccess(List<PacketProblemCreationLog> result) { ChangeHistoryView view = (ChangeHistoryView) panelChangeHistory.getWidget(); view.setCreationLog(result); } @Override public void onFailure(Throwable caught) { logger.log(Level.WARNING, "問題変更ログの取得に失敗しました", caught); } }; private void clearWarnings() { panelWarning.clear(); } private void addWarnings(String warning) { HTML w = new HTML(SafeHtmlUtils.fromString(warning)); w.addStyleName("gwt-HTML-problemCreationWarning"); panelWarning.add(w); } private void addInfo(String info) { HTML w = new HTML(SafeHtmlUtils.fromString(info)); w.addStyleName("gwt-HTML-problemCreationInfo"); panelWarning.add(w); } public void setProblem(int problemID) { textBoxGetProblem.setText(String.valueOf(problemID)); getProblemFromServer(false); } @Override protected void onLoad() { super.onLoad(); Scheduler.get().scheduleFixedDelay(commandCheckProblem, 1000); Scheduler.get().scheduleFixedDelay(commandCheckProblemId, 1000); // 連続投稿制限のチェック int userCode = UserData.get().getUserCode(); Service.Util.getInstance().canUploadProblem(userCode, null, callbackCanUploadProblemOnLoad); } private final AsyncCallback<Boolean> callbackCanUploadProblemOnLoad = new AsyncCallback<Boolean>() { @Override public void onSuccess(Boolean result) { if (result) { return; } showRepeatedPostWarning(); } @Override public void onFailure(Throwable caught) { logger.log(Level.WARNING, "連続投稿制限のチェックに失敗しました", caught); sendingProblem = false; widgetProblemForm.setEnable(true); setEnable(true); } }; private void showRepeatedPostWarning() { final DialogBox dialogBox = new DialogBox(true); VerticalPanel panel = new VerticalPanel(); panel.add(new HTML(new SafeHtmlBuilder().appendEscapedLines( "現在アニメジャンルにおいて連続投稿制限中です。\n" + "送信した問題が受け付けられない場合があります。\n" + "その他のジャンルは通常通り投稿できます。") .toSafeHtml())); panel.add(new Button("OK", new ClickHandler() { @Override public void onClick(ClickEvent event) { dialogBox.hide(); } })); dialogBox.setWidget(panel); dialogBox.setAnimationEnabled(true); dialogBox.setGlassEnabled(true); dialogBox.setHTML(SafeHtmlUtils.fromString("連続投稿制限")); dialogBox.setPopupPosition(100, 100); dialogBox.show(); } @UiHandler("buttonNewProblem") void onButtonNewProblem(ClickEvent e) { reset(); } @UiHandler("buttonMoveToVerification") void onButtonMoveToVerification(ClickEvent e) { if (!validateProblem()) { return; } buttonMoveToVerification.setText("問題を修正して再度送信確認画面に移動する"); buttonSendProblem.setVisible(true); PacketProblem problem = widgetProblemForm.getProblem(); setProblemSample(problem); panelSimilar.clear(); getSimilarProblems(problem); } @UiHandler("buttonSendProblem") void onButtonSendProblem(ClickEvent e) { if (!validateProblem()) { return; } sendingProblem = true; widgetProblemForm.setEnable(false); setEnable(false); PacketProblem problem = widgetProblemForm.getProblem(); int userCode = UserData.get().getUserCode(); if (problem.genre == ProblemGenre.Anige) { // アニゲの場合、連続投稿制限に引っかかっていないかどうかのチェック Service.Util.getInstance().canUploadProblem(userCode, problem.id == -1 ? null : problem.id, callbackCanUploadProblem); } else { uploadProblem(); } } private final AsyncCallback<Boolean> callbackCanUploadProblem = new AsyncCallback<Boolean>() { @Override public void onSuccess(Boolean result) { if (!result) { Window.alert("連続投稿制限: 時間を置いて再度お試しください\n" + "http://kishibe.dyndns.tv/QMAClone/ からアクセスしている場合は\n" + "http://kishibe.dyndns.tv:8080/QMAClone/ から御アクセスください。"); sendingProblem = false; widgetProblemForm.setEnable(true); setEnable(true); return; } uploadProblem(); } @Override public void onFailure(Throwable caught) { logger.log(Level.WARNING, "連続投稿制限のチェックに失敗しました", caught); sendingProblem = false; widgetProblemForm.setEnable(true); setEnable(true); } }; private void uploadProblem() { PacketProblem problem = widgetProblemForm.getProblem(); int userCode = UserData.get().getUserCode(); boolean resetAnswerCount = widgetProblemForm.isReserveResetAnswerCount(); // プレイヤー回答削除 if (problem.needsRemovePlayerAnswers) { Service.Util.getInstance().removePlayerAnswers(problem.id, callbackRemovePlayerAnswers); } // 良問悪問投票リセット if (problem.needsResetVote) { Service.Util.getInstance().resetVote(problem.id, callbackResetVote); } Service.Util.getInstance().uploadProblem(problem, userCode, resetAnswerCount, callbackUploadProblem); } private final AsyncCallback<Void> callbackRemovePlayerAnswers = new AsyncCallback<Void>() { @Override public void onSuccess(Void result) { } @Override public void onFailure(Throwable caught) { logger.log(Level.WARNING, "誤解答の削除に失敗しました", caught); } }; private final AsyncCallback<Boolean> callbackResetProblemCorrectCounter = new AsyncCallback<Boolean>() { @Override public void onSuccess(Boolean result) { if (!result) { Window.alert("回答数リセット回数の上限に達しました。\n回答数はリセットされませんでした。\n時間をおいて操作をしてください。"); } } @Override public void onFailure(Throwable caught) { logger.log(Level.WARNING, "問題正答数リセット権限のチェックに失敗しました"); } }; private final AsyncCallback<Void> callbackResetVote = new AsyncCallback<Void>() { @Override public void onSuccess(Void result) { } @Override public void onFailure(Throwable caught) { logger.log(Level.WARNING, "投票のリセットに失敗しました", caught); } }; @UiHandler("buttonGetProblem") void onButtonGetProblem(ClickEvent e) { getProblemFromServer(false); } @UiHandler("buttonCopyProblem") void onButtonCopyProblem(ClickEvent e) { getProblemFromServer(true); } @UiHandler("buttonNextProblem") void onButtonNextProblem(ClickEvent e) { reset(); // 連続投稿制限のチェック int userCode = UserData.get().getUserCode(); Service.Util.getInstance().canUploadProblem(userCode, null, callbackCanUploadProblemOnLoad); } private boolean checkProblemId() { try { Integer.parseInt(textBoxGetProblem.getText()); } catch (NumberFormatException e) { return false; } return true; } public void reloadBbs() { PanelBbs bbs = (PanelBbs) panelBbs.getWidget(); if (bbs == null) { return; } bbs.reload(); } @Override public void onUpdateDiffTarget(PacketProblemCreationLog before, PacketProblemCreationLog after) { if (before == null || after == null) { return; } Service.Util.getInstance().generateDiffHtml(before.summary, after.summary, callbackGenerateDiffHtml); } private final AsyncCallback<String> callbackGenerateDiffHtml = new AsyncCallback<String>() { @Override public void onSuccess(String result) { ChangeHistoryView view = (ChangeHistoryView) panelChangeHistory.getWidget(); view.setDiffHtml(SafeHtmlUtils.fromSafeConstant(result)); } @Override public void onFailure(Throwable caught) { logger.log(Level.WARNING, "差分htmlの生成に失敗しました", caught); } }; }