From ef6544ee27aeab20a64b4df4bd50401cefa405ef Mon Sep 17 00:00:00 2001 From: BogDan Vatra Date: Sat, 9 Nov 2013 09:21:02 +0200 Subject: Android native message dialog Change-Id: Ief8c3ce3b8683c6960f046245844c1835a327d51 Reviewed-by: Shawn Rutledge --- src/android/jar/jar.pri | 1 + .../qt5/android/QtMessageDialogHelper.java | 425 +++++++++++++++++++++ .../qtproject/qt5/android/bindings/QtActivity.java | 6 +- .../platforms/android/src/androidjnimain.cpp | 4 +- .../android/src/qandroidplatformdialoghelpers.cpp | 223 +++++++++++ .../android/src/qandroidplatformdialoghelpers.h | 77 ++++ .../android/src/qandroidplatformtheme.cpp | 18 + .../platforms/android/src/qandroidplatformtheme.h | 3 + src/plugins/platforms/android/src/src.pri | 2 + src/widgets/dialogs/qdialog.cpp | 24 ++ src/widgets/dialogs/qdialog_p.h | 3 +- src/widgets/dialogs/qfiledialog.cpp | 4 +- src/widgets/dialogs/qfiledialog_p.h | 2 +- src/widgets/dialogs/qmessagebox.cpp | 66 +++- src/widgets/dialogs/qmessagebox.h | 1 + 15 files changed, 848 insertions(+), 11 deletions(-) create mode 100644 src/android/jar/src/org/qtproject/qt5/android/QtMessageDialogHelper.java create mode 100644 src/plugins/platforms/android/src/qandroidplatformdialoghelpers.cpp create mode 100644 src/plugins/platforms/android/src/qandroidplatformdialoghelpers.h diff --git a/src/android/jar/jar.pri b/src/android/jar/jar.pri index 19501d7b29..5906231c73 100644 --- a/src/android/jar/jar.pri +++ b/src/android/jar/jar.pri @@ -9,6 +9,7 @@ JAVASOURCES += \ $$PATHPREFIX/QtEditText.java \ $$PATHPREFIX/QtInputConnection.java \ $$PATHPREFIX/QtLayout.java \ + $$PATHPREFIX/QtMessageDialogHelper.java \ $$PATHPREFIX/QtNative.java \ $$PATHPREFIX/QtNativeLibrariesDir.java \ $$PATHPREFIX/QtSurface.java diff --git a/src/android/jar/src/org/qtproject/qt5/android/QtMessageDialogHelper.java b/src/android/jar/src/org/qtproject/qt5/android/QtMessageDialogHelper.java new file mode 100644 index 0000000000..6ee1304c12 --- /dev/null +++ b/src/android/jar/src/org/qtproject/qt5/android/QtMessageDialogHelper.java @@ -0,0 +1,425 @@ +/**************************************************************************** + ** + ** Copyright (C) 2013 BogDan Vatra + ** Contact: http://www.qt-project.org/legal + ** + ** This file is part of the Android port of the Qt Toolkit. + ** + ** $QT_BEGIN_LICENSE:LGPL$ + ** Commercial License Usage + ** Licensees holding valid commercial Qt licenses may use this file in + ** accordance with the commercial license agreement provided with the + ** Software or, alternatively, in accordance with the terms contained in + ** a written agreement between you and Digia. For licensing terms and + ** conditions see http://qt.digia.com/licensing. For further information + ** use the contact form at http://qt.digia.com/contact-us. + ** + ** GNU Lesser General Public License Usage + ** Alternatively, this file may be used under the terms of the GNU Lesser + ** General Public License version 2.1 as published by the Free Software + ** Foundation and appearing in the file LICENSE.LGPL included in the + ** packaging of this file. Please review the following information to + ** ensure the GNU Lesser General Public License version 2.1 requirements + ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. + ** + ** In addition, as a special exception, Digia gives you certain additional + ** rights. These rights are described in the Digia Qt LGPL Exception + ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. + ** + ** GNU General Public License Usage + ** Alternatively, this file may be used under the terms of the GNU + ** General Public License version 3.0 as published by the Free Software + ** Foundation and appearing in the file LICENSE.GPL included in the + ** packaging of this file. Please review the following information to + ** ensure the GNU General Public License version 3.0 requirements will be + ** met: http://www.gnu.org/copyleft/gpl.html. + ** + ** + ** $QT_END_LICENSE$ + ** + ****************************************************************************/ + + +package org.qtproject.qt5.android; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.text.ClipboardManager; +import android.util.TypedValue; +import android.view.View; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.ScrollView; +import android.widget.TextView; +import android.widget.Toast; + +import java.util.ArrayList; + +class QtNativeDialogHelper +{ + static native void dialogResult(long handler, int buttonID); +} + +class ButtonStruct implements View.OnClickListener +{ + ButtonStruct(QtMessageDialogHelper dialog, int id, String text) + { + m_dialog = dialog; + m_id = id; + m_text = text; + } + QtMessageDialogHelper m_dialog; + private int m_id; + String m_text; + + @Override + public void onClick(View view) { + QtNativeDialogHelper.dialogResult(m_dialog.handler(), m_id); + } +} + +public class QtMessageDialogHelper +{ + + public QtMessageDialogHelper(Activity activity) + { + m_activity = activity; + } + + + public void setIcon(int icon) + { + m_icon = icon; + + } + + private Drawable getIconDrawable() + { + if (m_icon == 0) + return null; + + if (Build.VERSION.SDK_INT > 10) { + try { + TypedValue typedValue = new TypedValue(); + m_theme.resolveAttribute(Class.forName("android.R$attr").getDeclaredField("alertDialogIcon").getInt(null), typedValue, true); + return m_activity.getResources().getDrawable(typedValue.resourceId); + } catch (Exception e) { + e.printStackTrace(); + } + } + + // Information, Warning, Critical, Question + switch (m_icon) + { + case 1: // Information + try { + return m_activity.getResources().getDrawable(Class.forName("android.R$drawable").getDeclaredField("ic_dialog_info").getInt(null)); + } catch (Exception e) { + e.printStackTrace(); + } + break; + case 2: // Warning +// try { +// return Class.forName("android.R$drawable").getDeclaredField("stat_sys_warning").getInt(null); +// } catch (Exception e) { +// e.printStackTrace(); +// } +// break; + case 3: // Critical + try { + return m_activity.getResources().getDrawable(Class.forName("android.R$drawable").getDeclaredField("ic_dialog_alert").getInt(null)); + } catch (Exception e) { + e.printStackTrace(); + } + break; + case 4: // Question + try { + return m_activity.getResources().getDrawable(Class.forName("android.R$drawable").getDeclaredField("ic_menu_help").getInt(null)); + } catch (Exception e) { + e.printStackTrace(); + } + break; + } + return null; + } + + public void setTile(String title) + { + m_title = title; + } + + public void setText(String text) + { + m_text = text; + } + + public void setInformativeText(String informativeText) + { + m_informativeText = informativeText; + } + + public void setDetailedText(String text) + { + m_detailedText = text; + } + + public void addButton(int id, String text) + { + if (m_buttonsList == null) + m_buttonsList = new ArrayList(); + m_buttonsList.add(new ButtonStruct(this, id, text)); + } + + private void setTextAppearance(TextView view, String attr, String style) + { + try { + int[] attrs = (int[]) Class.forName("android.R$styleable").getDeclaredField("TextAppearance").get(null); + final TypedArray a = m_theme.obtainStyledAttributes(null, + attrs, + Class.forName("android.R$attr").getDeclaredField(attr).getInt(null), + Class.forName("android.R$style").getDeclaredField(style).getInt(null)); + final int textSize = a.getDimensionPixelSize( + Class.forName("android.R$styleable").getDeclaredField("TextAppearance_textSize").getInt(null), 0); + if (textSize != 0) + view.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); + + final int textColor = a.getColor( + Class.forName("android.R$styleable").getDeclaredField("TextAppearance_textColor").getInt(null), 0x3138); + if (textColor != 0x3138) + view.setTextColor(textColor); + + a.recycle(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private Drawable getStyledDrawable(String drawable) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException + { + int[] attrs = {Class.forName("android.R$attr").getDeclaredField(drawable).getInt(null)}; + final TypedArray a = m_theme.obtainStyledAttributes(attrs); + Drawable d = a.getDrawable(0); + a.recycle(); + return d; + } + + + public void show(long handler) + { + m_handler = handler; + m_activity.runOnUiThread( new Runnable() { + @Override + public void run() { + if (m_dialog != null && m_dialog.isShowing()) + m_dialog.dismiss(); + + m_dialog = new AlertDialog.Builder(m_activity).create(); + m_theme = m_dialog.getWindow().getContext().getTheme(); + + if (m_title != null) + m_dialog.setTitle(m_title); + m_dialog.setOnCancelListener( new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialogInterface) { + QtNativeDialogHelper.dialogResult(handler(), -1); + } + }); + m_dialog.setCancelable(m_buttonsList == null); + m_dialog.setCanceledOnTouchOutside(m_buttonsList == null); + m_dialog.setIcon(getIconDrawable()); + ScrollView scrollView = new ScrollView(m_activity); + RelativeLayout dialogLayout = new RelativeLayout(m_activity); + int id = 1; + View lastView = null; + View.OnLongClickListener copyText = new View.OnLongClickListener() { + @Override + public boolean onLongClick(View view) { + TextView tv = (TextView)view; + if (tv != null) { + ClipboardManager cm = (android.text.ClipboardManager) m_activity.getSystemService(Context.CLIPBOARD_SERVICE); + cm.setText(tv.getText()); + } + return true; + } + }; + if (m_text != null) + { + TextView view = new TextView(m_activity); + view.setId(id++); + view.setOnLongClickListener(copyText); + view.setLongClickable(true); + + view.setText(m_text); + setTextAppearance(view, "textAppearanceMedium", "TextAppearance_Medium"); + + RelativeLayout.LayoutParams layout = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT); + layout.setMargins(16, 8, 16, 8); + layout.addRule(RelativeLayout.ALIGN_PARENT_TOP); + dialogLayout.addView(view, layout); + lastView = view; + } + + if (m_informativeText != null) + { + TextView view= new TextView(m_activity); + view.setId(id++); + view.setOnLongClickListener(copyText); + view.setLongClickable(true); + + view.setText(m_informativeText); + setTextAppearance(view, "textAppearanceMedium", "TextAppearance_Medium"); + + RelativeLayout.LayoutParams layout = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT); + layout.setMargins(16, 8, 16, 8); + if (lastView != null) + layout.addRule(RelativeLayout.BELOW, lastView.getId()); + else + layout.addRule(RelativeLayout.ALIGN_PARENT_TOP); + dialogLayout.addView(view, layout); + lastView = view; + } + + if (m_detailedText != null) + { + TextView view= new TextView(m_activity); + view.setId(id++); + view.setOnLongClickListener(copyText); + view.setLongClickable(true); + + view.setText(m_detailedText); + setTextAppearance(view, "textAppearanceSmall", "TextAppearance_Small"); + + RelativeLayout.LayoutParams layout = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT); + layout.setMargins(16, 8, 16, 8); + if (lastView != null) + layout.addRule(RelativeLayout.BELOW, lastView.getId()); + else + layout.addRule(RelativeLayout.ALIGN_PARENT_TOP); + dialogLayout.addView(view, layout); + lastView = view; + } + + if (m_buttonsList != null) + { + LinearLayout buttonsLayout = new LinearLayout(m_activity); + buttonsLayout.setOrientation(LinearLayout.HORIZONTAL); + buttonsLayout.setId(id++); + boolean firstButton = true; + for (ButtonStruct button: m_buttonsList) + { + Button bv; + if (Build.VERSION.SDK_INT > 10) { + try { + bv = new Button(m_activity, null, Class.forName("android.R$attr").getDeclaredField("borderlessButtonStyle").getInt(null)); + } catch (Exception e) { + bv = new Button(m_activity); + e.printStackTrace(); + } + } else { + bv = new Button(m_activity); + } + + bv.setText(button.m_text); + bv.setOnClickListener(button); + if (!firstButton) // first button + { + LinearLayout.LayoutParams layout = null; + View spacer = new View(m_activity); + if (Build.VERSION.SDK_INT > 10) { + try { + layout = new LinearLayout.LayoutParams(1, RelativeLayout.LayoutParams.MATCH_PARENT); + spacer.setBackgroundDrawable(getStyledDrawable("dividerVertical")); + buttonsLayout.addView(spacer, layout); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + LinearLayout.LayoutParams layout = null; + layout = new LinearLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT, 1.0f); + buttonsLayout.addView(bv, layout); + firstButton = false; + } + + if (Build.VERSION.SDK_INT > 10) { + try { + View horizontalDevider = new View(m_activity); + horizontalDevider.setId(id++); + horizontalDevider.setBackgroundDrawable(getStyledDrawable("dividerHorizontal")); + RelativeLayout.LayoutParams relativeParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, 1); + relativeParams.setMargins(0, 10, 0, 0); + if (lastView != null) { + relativeParams.addRule(RelativeLayout.BELOW, lastView.getId()); + } + else + relativeParams.addRule(RelativeLayout.ALIGN_PARENT_TOP); + dialogLayout.addView(horizontalDevider, relativeParams); + lastView = horizontalDevider; + } catch (Exception e) { + e.printStackTrace(); + } + } + RelativeLayout.LayoutParams relativeParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT); + if (lastView != null) { + relativeParams.addRule(RelativeLayout.BELOW, lastView.getId()); + } + else + relativeParams.addRule(RelativeLayout.ALIGN_PARENT_TOP); + if (Build.VERSION.SDK_INT < 11) + relativeParams.setMargins(2, 12, 2, 4); + else + relativeParams.setMargins(2, 0, 2, 0); + dialogLayout.addView(buttonsLayout, relativeParams); + } + scrollView.addView(dialogLayout); + m_dialog.setView(scrollView); + m_dialog.show(); + } + }); + } + + public void hide() + { + m_activity.runOnUiThread( new Runnable() { + @Override + public void run() { + if (m_dialog != null && m_dialog.isShowing()) + m_dialog.dismiss(); + reset(); + } + }); + } + + public long handler() + { + return m_handler; + } + + public void reset() + { + m_icon = 0; + m_title = null; + m_text = null; + m_informativeText = null; + m_detailedText = null; + m_buttonsList = null; + m_dialog = null; + m_handler = 0; + } + + private Activity m_activity; + private int m_icon = 0; + private String m_title, m_text, m_informativeText, m_detailedText; + private ArrayList m_buttonsList; + private AlertDialog m_dialog; + private long m_handler = 0; + private Resources.Theme m_theme; +} diff --git a/src/android/java/src/org/qtproject/qt5/android/bindings/QtActivity.java b/src/android/java/src/org/qtproject/qt5/android/bindings/QtActivity.java index 089cf5aa60..7c741edfda 100644 --- a/src/android/java/src/org/qtproject/qt5/android/bindings/QtActivity.java +++ b/src/android/java/src/org/qtproject/qt5/android/bindings/QtActivity.java @@ -123,13 +123,13 @@ public class QtActivity extends Activity // and must be separated with "\t" // e.g "-param1\t-param2=value2\t-param3\tvalue3" - public String ENVIRONMENT_VARIABLES = "QT_USE_ANDROID_NATIVE_STYLE=1\t"; + public String ENVIRONMENT_VARIABLES = "QT_USE_ANDROID_NATIVE_STYLE=1\tQT_USE_ANDROID_NATIVE_DIALOGS=1\t"; // use this variable to add any environment variables to your application. // the env vars must be separated with "\t" // e.g. "ENV_VAR1=1\tENV_VAR2=2\t" // Currently the following vars are used by the android plugin: - // * QT_USE_ANDROID_NATIVE_STYLE - 1 to use the android widget style if available, - // note that the android style plugin in Qt 5.1 is not fully functional. + // * QT_USE_ANDROID_NATIVE_STYLE - 1 to use the android widget style if available. + // * QT_USE_ANDROID_NATIVE_DIALOGS -1 to use the android native dialogs. public String[] QT_ANDROID_THEMES = null; // A list with all themes that your application want to use. // The name of the theme must be the same with any theme from diff --git a/src/plugins/platforms/android/src/androidjnimain.cpp b/src/plugins/platforms/android/src/androidjnimain.cpp index 3d599b8f8b..3ab4eedb26 100644 --- a/src/plugins/platforms/android/src/androidjnimain.cpp +++ b/src/plugins/platforms/android/src/androidjnimain.cpp @@ -59,6 +59,7 @@ #include "androidjniinput.h" #include "androidjniclipboard.h" #include "androidjnimenu.h" +#include "qandroidplatformdialoghelpers.h" #include "qandroidplatformintegration.h" #include @@ -865,7 +866,8 @@ Q_DECL_EXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void */*reserved*/) || !QtAndroidInput::registerNatives(env) || !QtAndroidClipboard::registerNatives(env) || !QtAndroidMenu::registerNatives(env) - ) { + || !QtAndroidAccessibility::registerNatives(env) + || !QtAndroidDialogHelpers::registerNatives(env)) { __android_log_print(ANDROID_LOG_FATAL, "Qt", "registerNatives failed"); return -1; } diff --git a/src/plugins/platforms/android/src/qandroidplatformdialoghelpers.cpp b/src/plugins/platforms/android/src/qandroidplatformdialoghelpers.cpp new file mode 100644 index 0000000000..f9eb34751e --- /dev/null +++ b/src/plugins/platforms/android/src/qandroidplatformdialoghelpers.cpp @@ -0,0 +1,223 @@ +/**************************************************************************** +** +** Copyright (C) 2013 BogDan Vatra +** Contact: http://www.qt-project.org/legal +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include +#include +#include "qandroidplatformdialoghelpers.h" +#include "androidjnimain.h" + +namespace QtAndroidDialogHelpers { +static jclass g_messageDialogHelperClass = 0; + +QAndroidPlatformMessageDialogHelper::QAndroidPlatformMessageDialogHelper() + :m_buttonId(-1) + ,m_javaMessageDialog(g_messageDialogHelperClass, "(Landroid/app/Activity;)V", QtAndroid::activity()) + ,m_shown(false) +{ +} + +void QAndroidPlatformMessageDialogHelper::exec() +{ + if (!m_shown) + show(Qt::Dialog, Qt::ApplicationModal, 0); + m_loop.exec(); +} + +static QString standardButtonText(int sbutton) +{ + QString buttonText = 0; + switch (sbutton) { + case QMessageDialogOptions::Ok: + buttonText = QObject::tr("OK"); + break; + case QMessageDialogOptions::Save: + buttonText = QObject::tr("Save"); + break; + case QMessageDialogOptions::Open: + buttonText = QObject::tr("Open"); + break; + case QMessageDialogOptions::Cancel: + buttonText = QObject::tr("Cancel"); + break; + case QMessageDialogOptions::Close: + buttonText = QObject::tr("Close"); + break; + case QMessageDialogOptions::Apply: + buttonText = QObject::tr("Apply"); + break; + case QMessageDialogOptions::Reset: + buttonText = QObject::tr("Reset"); + break; + case QMessageDialogOptions::Help: + buttonText = QObject::tr("Help"); + break; + case QMessageDialogOptions::Discard: + buttonText = QObject::tr("Discard"); + break; + case QMessageDialogOptions::Yes: + buttonText = QObject::tr("Yes"); + break; + case QMessageDialogOptions::YesToAll: + buttonText = QObject::tr("Yes to All"); + break; + case QMessageDialogOptions::No: + buttonText = QObject::tr("No"); + break; + case QMessageDialogOptions::NoToAll: + buttonText = QObject::tr("No to All"); + break; + case QMessageDialogOptions::SaveAll: + buttonText = QObject::tr("Save All"); + break; + case QMessageDialogOptions::Abort: + buttonText = QObject::tr("Abort"); + break; + case QMessageDialogOptions::Retry: + buttonText = QObject::tr("Retry"); + break; + case QMessageDialogOptions::Ignore: + buttonText = QObject::tr("Ignore"); + break; + case QMessageDialogOptions::RestoreDefaults: + buttonText = QObject::tr("Restore Defaults"); + break; + } // switch + return buttonText; +} + +bool QAndroidPlatformMessageDialogHelper::show(Qt::WindowFlags windowFlags + , Qt::WindowModality windowModality + , QWindow *parent) +{ + Q_UNUSED(windowFlags) + Q_UNUSED(windowModality) + Q_UNUSED(parent) + QSharedPointer opt = options(); + if (!opt.data()) + return false; + + m_javaMessageDialog.callMethod("setIcon", "(I)V", opt->icon()); + + QString str = opt->windowTitle(); + if (!str.isEmpty()) + m_javaMessageDialog.callMethod("setTile", "(Ljava/lang/String;)V", QJNIObjectPrivate::fromString(str).object()); + + str = opt->text(); + if (!str.isEmpty()) + m_javaMessageDialog.callMethod("setText", "(Ljava/lang/String;)V", QJNIObjectPrivate::fromString(str).object()); + + str = opt->informativeText(); + if (!str.isEmpty()) + m_javaMessageDialog.callMethod("setInformativeText", "(Ljava/lang/String;)V", QJNIObjectPrivate::fromString(str).object()); + + str = opt->detailedText(); + if (!str.isEmpty()) + m_javaMessageDialog.callMethod("setDetailedText", "(Ljava/lang/String;)V", QJNIObjectPrivate::fromString(str).object()); + + for (int i = QMessageDialogOptions::FirstButton; i < QMessageDialogOptions::LastButton; i<<=1) { + if ( opt->standardButtons() & i ) + m_javaMessageDialog.callMethod("addButton", "(ILjava/lang/String;)V", i, QJNIObjectPrivate::fromString(standardButtonText(i)).object()); + } + + m_javaMessageDialog.callMethod("show", "(J)V", jlong(static_cast(this))); + m_shown = true; + return true; +} + +void QAndroidPlatformMessageDialogHelper::hide() +{ + m_javaMessageDialog.callMethod("hide", "()V"); + m_shown = false; +} + +void QAndroidPlatformMessageDialogHelper::dialogResult(int buttonID) +{ + m_buttonId = buttonID; + if (m_loop.isRunning()) + m_loop.exit(); + if (m_buttonId < 0) { + emit reject(); + return; + } + + QMessageDialogOptions::StandardButton standardButton = static_cast(buttonID); + QMessageDialogOptions::ButtonRole role = QMessageDialogOptions::buttonRole(standardButton); + emit clicked(standardButton, role); +} + +static void dialogResult(JNIEnv * /*env*/, jobject /*thiz*/, jlong handler, int buttonID) +{ + QObject *object = reinterpret_cast(handler); + QMetaObject::invokeMethod(object, "dialogResult", Qt::QueuedConnection, Q_ARG(int, buttonID)); +} + +static JNINativeMethod methods[] = { + {"dialogResult", "(JI)V", (void *)dialogResult} +}; + + +#define FIND_AND_CHECK_CLASS(CLASS_NAME) \ + clazz = env->FindClass(CLASS_NAME); \ + if (!clazz) { \ + __android_log_print(ANDROID_LOG_FATAL, QtAndroid::qtTagText(), QtAndroid::classErrorMsgFmt(), CLASS_NAME); \ + return false; \ + } + +bool registerNatives(JNIEnv *env) +{ + jclass clazz = QtAndroid::findClass("org/qtproject/qt5/android/QtMessageDialogHelper", env); + if (!clazz) { + __android_log_print(ANDROID_LOG_FATAL, QtAndroid::qtTagText(), QtAndroid::classErrorMsgFmt() + , "org/qtproject/qt5/android/QtMessageDialogHelper"); + return false; + } + g_messageDialogHelperClass = static_cast(env->NewGlobalRef(clazz)); + FIND_AND_CHECK_CLASS("org/qtproject/qt5/android/QtNativeDialogHelper"); + jclass appClass = static_cast(env->NewGlobalRef(clazz)); + + if (env->RegisterNatives(appClass, methods, sizeof(methods) / sizeof(methods[0])) < 0) { + __android_log_print(ANDROID_LOG_FATAL, "Qt", "RegisterNatives failed"); + return false; + } + + return true; +} +} diff --git a/src/plugins/platforms/android/src/qandroidplatformdialoghelpers.h b/src/plugins/platforms/android/src/qandroidplatformdialoghelpers.h new file mode 100644 index 0000000000..88ec91d936 --- /dev/null +++ b/src/plugins/platforms/android/src/qandroidplatformdialoghelpers.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2013 BogDan Vatra +** Contact: http://www.qt-project.org/legal +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QANDROIDPLATFORMDIALOGHELPERS_H +#define QANDROIDPLATFORMDIALOGHELPERS_H +#include +#include +#include +#include + +namespace QtAndroidDialogHelpers { + +class QAndroidPlatformMessageDialogHelper: public QPlatformMessageDialogHelper +{ + Q_OBJECT +public: + QAndroidPlatformMessageDialogHelper(); + void exec(); + bool show(Qt::WindowFlags windowFlags, + Qt::WindowModality windowModality, + QWindow *parent); + void hide(); + +public slots: + void dialogResult(int buttonID); + +private: + int m_buttonId; + QEventLoop m_loop; + QJNIObjectPrivate m_javaMessageDialog; + bool m_shown; +}; + + +bool registerNatives(JNIEnv *env); + +} + +#endif // QANDROIDPLATFORMDIALOGHELPERS_H diff --git a/src/plugins/platforms/android/src/qandroidplatformtheme.cpp b/src/plugins/platforms/android/src/qandroidplatformtheme.cpp index 79fbc440fc..0ceac97e35 100644 --- a/src/plugins/platforms/android/src/qandroidplatformtheme.cpp +++ b/src/plugins/platforms/android/src/qandroidplatformtheme.cpp @@ -43,6 +43,7 @@ #include "qandroidplatformmenubar.h" #include "qandroidplatformmenu.h" #include "qandroidplatformmenuitem.h" +#include "qandroidplatformdialoghelpers.h" #include #include #include @@ -150,3 +151,20 @@ QVariant QAndroidPlatformTheme::themeHint(ThemeHint hint) const return QPlatformTheme::themeHint(hint); } } + +bool QAndroidPlatformTheme::usePlatformNativeDialog(QPlatformTheme::DialogType type) const +{ + if (type == MessageDialog) + return qgetenv("QT_USE_ANDROID_NATIVE_DIALOGS").toInt() == 1; + return false; +} + +QPlatformDialogHelper *QAndroidPlatformTheme::createPlatformDialogHelper(QPlatformTheme::DialogType type) const +{ + switch (type) { + case MessageDialog: + return new QtAndroidDialogHelpers::QAndroidPlatformMessageDialogHelper; + default: + return 0; + } +} diff --git a/src/plugins/platforms/android/src/qandroidplatformtheme.h b/src/plugins/platforms/android/src/qandroidplatformtheme.h index 15d2cb2000..ec259a9b0a 100644 --- a/src/plugins/platforms/android/src/qandroidplatformtheme.h +++ b/src/plugins/platforms/android/src/qandroidplatformtheme.h @@ -54,6 +54,9 @@ public: virtual const QPalette *palette(Palette type = SystemPalette) const; virtual const QFont *font(Font type = SystemFont) const; virtual QVariant themeHint(ThemeHint hint) const; + virtual bool usePlatformNativeDialog(DialogType type) const; + virtual QPlatformDialogHelper *createPlatformDialogHelper(DialogType type) const; + private: QAndroidPlatformNativeInterface * m_androidPlatformNativeInterface; diff --git a/src/plugins/platforms/android/src/src.pri b/src/plugins/platforms/android/src/src.pri index 6cc41c3e68..9b64e846f7 100644 --- a/src/plugins/platforms/android/src/src.pri +++ b/src/plugins/platforms/android/src/src.pri @@ -21,6 +21,7 @@ SOURCES += $$PWD/androidplatformplugin.cpp \ $$PWD/qandroidinputcontext.cpp \ $$PWD/qandroidplatformaccessibility.cpp \ $$PWD/qandroidplatformfontdatabase.cpp \ + $$PWD/qandroidplatformdialoghelpers.cpp \ $$PWD/qandroidplatformclipboard.cpp \ $$PWD/qandroidplatformtheme.cpp \ $$PWD/qandroidplatformmenubar.cpp \ @@ -41,6 +42,7 @@ HEADERS += $$PWD/qandroidplatformintegration.h \ $$PWD/qandroidplatformaccessibility.h \ $$PWD/qandroidplatformfontdatabase.h \ $$PWD/qandroidplatformclipboard.h \ + $$PWD/qandroidplatformdialoghelpers.h \ $$PWD/qandroidplatformtheme.h \ $$PWD/qandroidplatformmenubar.h \ $$PWD/qandroidplatformmenu.h \ diff --git a/src/widgets/dialogs/qdialog.cpp b/src/widgets/dialogs/qdialog.cpp index af352e45c6..d498b077fc 100644 --- a/src/widgets/dialogs/qdialog.cpp +++ b/src/widgets/dialogs/qdialog.cpp @@ -52,6 +52,8 @@ #include "qwhatsthis.h" #include "qmenu.h" #include "qcursor.h" +#include "qmessagebox.h" +#include "qerrormessage.h" #include #include "private/qdialog_p.h" #include "private/qguiapplication_p.h" @@ -74,6 +76,14 @@ static inline int themeDialogType(const QDialog *dialog) #ifndef QT_NO_FONTDIALOG if (qobject_cast(dialog)) return QPlatformTheme::FontDialog; +#endif +#ifndef QT_NO_MESSAGEBOX + if (qobject_cast(dialog)) + return QPlatformTheme::MessageDialog; +#endif +#ifndef QT_NO_ERRORMESSAGE + if (qobject_cast(dialog)) + return QPlatformTheme::MessageDialog; #endif return -1; } @@ -100,6 +110,17 @@ QPlatformDialogHelper *QDialogPrivate::platformHelper() const return m_platformHelper; } +bool QDialogPrivate::canBeNativeDialog() const +{ + QDialogPrivate *ncThis = const_cast(this); + QDialog *dialog = ncThis->q_func(); + const int type = themeDialogType(dialog); + if (type >= 0) + return QGuiApplicationPrivate::platformTheme() + ->usePlatformNativeDialog(static_cast(type)); + return false; +} + QWindow *QDialogPrivate::parentWindow() const { if (const QWidget *parent = q_func()->nativeParentWidget()) @@ -697,6 +718,9 @@ void QDialog::closeEvent(QCloseEvent *e) void QDialog::setVisible(bool visible) { Q_D(QDialog); + if (!testAttribute(Qt::WA_DontShowOnScreen) && d->canBeNativeDialog() && d->setNativeDialogVisible(visible)) + return; + if (visible) { if (testAttribute(Qt::WA_WState_ExplicitShowHide) && !testAttribute(Qt::WA_WState_Hidden)) return; diff --git a/src/widgets/dialogs/qdialog_p.h b/src/widgets/dialogs/qdialog_p.h index 5064efa35d..8db1b2a27c 100644 --- a/src/widgets/dialogs/qdialog_p.h +++ b/src/widgets/dialogs/qdialog_p.h @@ -64,7 +64,7 @@ QT_BEGIN_NAMESPACE class QSizeGrip; -class QDialogPrivate : public QWidgetPrivate +class Q_WIDGETS_EXPORT QDialogPrivate : public QWidgetPrivate { Q_DECLARE_PUBLIC(QDialog) public: @@ -113,6 +113,7 @@ public: bool nativeDialogInUse; QPlatformDialogHelper *platformHelper() const; + virtual bool canBeNativeDialog() const; private: virtual void initHelper(QPlatformDialogHelper *) {} diff --git a/src/widgets/dialogs/qfiledialog.cpp b/src/widgets/dialogs/qfiledialog.cpp index cfdc303f21..da026d23a6 100644 --- a/src/widgets/dialogs/qfiledialog.cpp +++ b/src/widgets/dialogs/qfiledialog.cpp @@ -764,9 +764,9 @@ void QFileDialogPrivate::emitFilesSelected(const QStringList &files) emit q->fileSelected(files.first()); } -bool QFileDialogPrivate::canBeNativeDialog() +bool QFileDialogPrivate::canBeNativeDialog() const { - Q_Q(QFileDialog); + Q_Q(const QFileDialog); if (nativeDialogInUse) return true; if (q->testAttribute(Qt::WA_DontShowOnScreen)) diff --git a/src/widgets/dialogs/qfiledialog_p.h b/src/widgets/dialogs/qfiledialog_p.h index 36336bdbf6..faa721572c 100644 --- a/src/widgets/dialogs/qfiledialog_p.h +++ b/src/widgets/dialogs/qfiledialog_p.h @@ -251,7 +251,7 @@ public: // setVisible_sys returns true if it ends up showing a native // dialog. Returning false means that a non-native dialog must be // used instead. - bool canBeNativeDialog(); + bool canBeNativeDialog() const; inline bool usingWidgets() const; void setDirectory_sys(const QUrl &directory); diff --git a/src/widgets/dialogs/qmessagebox.cpp b/src/widgets/dialogs/qmessagebox.cpp index 9101c568e0..ee2ef409d8 100644 --- a/src/widgets/dialogs/qmessagebox.cpp +++ b/src/widgets/dialogs/qmessagebox.cpp @@ -193,7 +193,6 @@ public: } }; - class QMessageBoxPrivate : public QDialogPrivate { Q_DECLARE_PUBLIC(QMessageBox) @@ -204,11 +203,13 @@ public: detailsText(0), #endif compatMode(false), autoAddOkButton(true), - detectedEscapeButton(0), informativeLabel(0) { } + detectedEscapeButton(0), informativeLabel(0), + options(new QMessageDialogOptions) { } void init(const QString &title = QString(), const QString &text = QString()); void setupLayout(); void _q_buttonClicked(QAbstractButton *); + void _q_clicked(QMessageDialogOptions::StandardButton button, QMessageDialogOptions::ButtonRole role); QAbstractButton *findButton(int button0, int button1, int button2, int flags); void addOldButtons(int button0, int button1, int button2); @@ -224,7 +225,6 @@ public: #ifdef Q_OS_WINCE void hideSpecial(); #endif - static int showOldMessageBox(QWidget *parent, QMessageBox::Icon icon, const QString &title, const QString &text, int button0, int button1, int button2); @@ -262,6 +262,11 @@ public: QPointer receiverToDisconnectOnClose; QByteArray memberToDisconnectOnClose; QByteArray signalToDisconnectOnClose; + QSharedPointer options; +private: + void initHelper(QPlatformDialogHelper *); + void helperPrepareShow(QPlatformDialogHelper *); + void helperDone(QDialog::DialogCode, QPlatformDialogHelper *); }; void QMessageBoxPrivate::init(const QString &title, const QString &text) @@ -519,6 +524,13 @@ void QMessageBoxPrivate::_q_buttonClicked(QAbstractButton *button) } } +void QMessageBoxPrivate::_q_clicked(QMessageDialogOptions::StandardButton button, QMessageDialogOptions::ButtonRole role) +{ + Q_UNUSED(role); + Q_Q(QMessageBox); + q->done(button); +} + /*! \class QMessageBox @@ -2682,6 +2694,54 @@ QPixmap QMessageBoxPrivate::standardIcon(QMessageBox::Icon icon, QMessageBox *mb return QPixmap(); } +void QMessageBoxPrivate::initHelper(QPlatformDialogHelper *h) +{ + Q_Q(QMessageBox); + QObject::connect(h, SIGNAL(clicked(QMessageDialogOptions::StandardButton, QMessageDialogOptions::ButtonRole)), + q, SLOT(_q_clicked(QMessageDialogOptions::StandardButton, QMessageDialogOptions::ButtonRole))); + static_cast(h)->setOptions(options); +} + +static QMessageDialogOptions::Icon helperIcon(QMessageBox::Icon i) +{ + switch (i) { + case QMessageBox::NoIcon: + return QMessageDialogOptions::NoIcon; + case QMessageBox::Information: + return QMessageDialogOptions::Information; + case QMessageBox::Warning: + return QMessageDialogOptions::Warning; + case QMessageBox::Critical: + return QMessageDialogOptions::Critical; + case QMessageBox::Question: + return QMessageDialogOptions::Question; + } + return QMessageDialogOptions::NoIcon; +} + +static QMessageDialogOptions::StandardButtons helperStandardButtons(QMessageBox * q) +{ + QMessageDialogOptions::StandardButtons buttons(int(q->standardButtons())); + return buttons; +} + +void QMessageBoxPrivate::helperPrepareShow(QPlatformDialogHelper *) +{ + Q_Q(QMessageBox); + options->setWindowTitle(q->windowTitle()); + options->setText(q->text()); + options->setInformativeText(q->informativeText()); + options->setDetailedText(q->detailedText()); + options->setIcon(helperIcon(q->icon())); + options->setStandardButtons(helperStandardButtons(q)); +} + +void QMessageBoxPrivate::helperDone(QDialog::DialogCode code, QPlatformDialogHelper *) +{ + Q_Q(QMessageBox); + clickedButton = q->button(QMessageBox::StandardButton(code)); +} + /*! \obsolete diff --git a/src/widgets/dialogs/qmessagebox.h b/src/widgets/dialogs/qmessagebox.h index 58be13426c..c5598a8f1d 100644 --- a/src/widgets/dialogs/qmessagebox.h +++ b/src/widgets/dialogs/qmessagebox.h @@ -309,6 +309,7 @@ protected: private: Q_PRIVATE_SLOT(d_func(), void _q_buttonClicked(QAbstractButton *)) + Q_PRIVATE_SLOT(d_func(), void _q_clicked(QMessageDialogOptions::StandardButton, QMessageDialogOptions::ButtonRole)) Q_DISABLE_COPY(QMessageBox) Q_DECLARE_PRIVATE(QMessageBox) -- cgit v1.2.3