diff --git a/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/AssemblyInitializer.cs b/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/AssemblyInitializer.cs
new file mode 100644
index 00000000..417e9244
--- /dev/null
+++ b/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/AssemblyInitializer.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using FastReport.Utils;
+
+namespace FastReport.Data
+{
+ public class GoogleAssemblyInitializer : AssemblyInitializerBase
+ {
+ public GoogleAssemblyInitializer()
+ {
+ RegisteredObjects.AddConnection(typeof(GoogleSheetsDataConnection));
+ }
+ }
+}
diff --git a/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/GoogleAuthService.cs b/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/GoogleAuthService.cs
new file mode 100644
index 00000000..55eec019
--- /dev/null
+++ b/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/GoogleAuthService.cs
@@ -0,0 +1,59 @@
+using Google.Apis.Auth.OAuth2;
+using Google.Apis.Sheets.v4;
+using System;
+using System.IO;
+using System.Threading;
+
+namespace FastReport.Data
+{
+ public class GoogleAuthService
+ {
+ #region Public Methods
+
+ ///
+ /// Getting a token by connecting to a .json file
+ ///
+ /// Path to the location of the .json file containing the Google client secret data
+ /// UserCredential with access token
+ public static UserCredential GetAccessToken(string path)
+ {
+ string[] scopes = new string[] { SheetsService.Scope.Spreadsheets };
+
+ using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read))
+ {
+ var credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
+ GoogleClientSecrets.FromStream(stream).Secrets,
+ scopes,
+ "userName",
+ CancellationToken.None).Result;
+
+ return credential;
+ }
+ }
+
+ ///
+ /// Getting a token over an OAuth 2.0 connection
+ ///
+ /// Google Client ID
+ /// Google Client Secret
+ /// UserCredential with access token
+ public static UserCredential GetAccessToken(string clientId, string clientSecret)
+ {
+ string[] scopes = new string[] { SheetsService.Scope.Spreadsheets };
+
+ var credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
+ new ClientSecrets
+ {
+ ClientId = clientId,
+ ClientSecret = clientSecret
+ },
+ scopes,
+ Environment.UserName,
+ CancellationToken.None).Result;
+
+ return credential;
+ }
+
+ #endregion Public Methods
+ }
+}
\ No newline at end of file
diff --git a/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/GoogleSheets.cs b/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/GoogleSheets.cs
new file mode 100644
index 00000000..7b396c07
--- /dev/null
+++ b/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/GoogleSheets.cs
@@ -0,0 +1,95 @@
+using Google.Apis.Auth.OAuth2;
+using Google.Apis.Services;
+using Google.Apis.Sheets.v4;
+using Google.Apis.Sheets.v4.Data;
+using System.Collections.Generic;
+using System.Windows.Forms;
+
+namespace FastReport.Data
+{
+ ///
+ /// Provides connection, reading and writing data from Google Sheets
+ ///
+ public class GoogleSheets
+ {
+ #region Private Fields
+
+ private static SheetsService service;
+
+ #endregion Private Fields
+
+ #region Public Method
+
+ ///
+ /// Initializing the Sheets service with an Access Token
+ ///
+ /// Contains an access token for the Google Sheets API
+ public static void InitService(UserCredential credential)
+ {
+ service = new SheetsService(new BaseClientService.Initializer()
+ {
+ ApplicationName = " ",
+ HttpClientInitializer = credential,
+ });
+ }
+
+ ///
+ /// Initializing the Sheets service using an API key
+ ///
+ /// Contains an API key to access the Google Sheets API
+ /// SheetsService for performing operations with the Google Sheets API
+ public static SheetsService InitService(string APIkey)
+ {
+ service = new SheetsService(new BaseClientService.Initializer()
+ {
+ ApplicationName = " ",
+ ApiKey = APIkey,
+ });
+
+ return service;
+ }
+
+ ///
+ /// Read data from a sheet specifying a range of cells
+ ///
+ /// Google Sheets Table ID
+ /// Range of cells to be read
+ /// A list of lists of objects (IList>) that contains the data read
+ public static IList> ReadData(string spreadsheetsId, string range)
+ {
+ var response = service.Spreadsheets.Values.Get(spreadsheetsId, range).Execute();
+
+ return response.Values;
+ }
+
+ ///
+ /// Read data from a sheet specifying a range of cells
+ ///
+ /// Google Sheets Table ID
+ /// Range start column to be read
+ /// Range end column to be read
+ /// A list of lists of objects (IList>) that contains the data read
+ public static IList> ReadData(string spreadsheetsId, string startColumn, string endColumn)
+ {
+ var range = startColumn + ":" + endColumn;
+
+ var response = service.Spreadsheets.Values.Get(spreadsheetsId, range).Execute();
+
+ return response.Values;
+ }
+
+ ///
+ /// Reading data from a table
+ ///
+ /// Google Sheets Table ID
+ /// Returns a Spreadsheet with information about the table
+ public static Spreadsheet ReadSpreadSheet (string spreadsheetsId)
+ {
+ var spreadsheet = service.Spreadsheets.Get(spreadsheetsId).Execute();
+
+ return spreadsheet;
+ }
+
+ #endregion Public Method
+ }
+}
\ No newline at end of file
diff --git a/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/GoogleSheetsConnectionEditor.cs b/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/GoogleSheetsConnectionEditor.cs
new file mode 100644
index 00000000..2cdd603b
--- /dev/null
+++ b/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/GoogleSheetsConnectionEditor.cs
@@ -0,0 +1,94 @@
+using System;
+using FastReport.Data.ConnectionEditors;
+using FastReport.Utils;
+
+namespace FastReport.Data
+{
+ internal partial class GoogleSheetsConnectionEditor : ConnectionEditorBase
+ {
+ #region Fields
+
+ private bool updating;
+
+ #endregion Fields
+
+ #region Constructors
+ public GoogleSheetsConnectionEditor()
+ {
+ updating = true;
+ InitializeComponent();
+ CheckSignInGoogleAPI();
+ Localize();
+ updating = false;
+ }
+
+ #endregion Constructors
+
+ #region Events Handlers
+
+ private void btSignInGoogle_Click(object sender, EventArgs e)
+ {
+ using (SignInGoogle signInGoogle = new SignInGoogle())
+ {
+ signInGoogle.ShowDialog();
+ }
+ }
+
+ #endregion Events Handlers
+
+ #region Protected Methods
+
+ protected override string GetConnectionString()
+ {
+ GoogleSheetsConnectionStringBuilder builder = new GoogleSheetsConnectionStringBuilder();
+ builder.Sheets = tbGoogleId.Text;
+ builder.FieldNamesInFirstString = cbxFieldNames.Checked;
+ return builder.ToString();
+ }
+
+ protected override void SetConnectionString(string value)
+ {
+ GoogleSheetsConnectionStringBuilder builder = new GoogleSheetsConnectionStringBuilder(value);
+ tbGoogleId.Text = builder.Sheets;
+ cbxFieldNames.Checked = builder.FieldNamesInFirstString;
+ }
+
+ #endregion Protected Methods
+
+ #region Private Methods
+
+ private void Localize()
+ {
+ MyRes res = new MyRes("ConnectionEditors,GoogleSheets");
+ gbSelect.Text = res.Get("ConfigureDatabase");
+ lblSelectGId.Text = res.Get("SelectSheetsId");
+ cbxFieldNames.Text = res.Get("FieldNames");
+ btSignInGoogle.Text = res.Get("SignIn");
+ }
+
+ private void CheckSignInGoogleAPI ()
+ {
+ XmlItem xi = Config.Root.FindItem("GoogleSheets").FindItem("StorageSettings");
+ string id = xi.GetProp("ClientId");
+ string secret = xi.GetProp("ClientSecret");
+ string pathToJson = xi.GetProp("PathToJson");
+ string apiKey = xi.GetProp("ApiKey");
+ if (String.IsNullOrEmpty(id) && String.IsNullOrEmpty(secret) && String.IsNullOrEmpty(pathToJson) && String.IsNullOrEmpty(apiKey))
+ {
+ using (SignInGoogle signInGoogle = new SignInGoogle())
+ signInGoogle.ShowDialog();
+ }
+ }
+
+ #endregion Private Methods
+
+ #region Public Methods
+
+ public override void UpdateDpiDependencies()
+ {
+ base.UpdateDpiDependencies();
+ }
+
+ #endregion Public Methods
+ }
+}
\ No newline at end of file
diff --git a/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/GoogleSheetsConnectionEditor.designer.cs b/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/GoogleSheetsConnectionEditor.designer.cs
new file mode 100644
index 00000000..83a8b3fa
--- /dev/null
+++ b/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/GoogleSheetsConnectionEditor.designer.cs
@@ -0,0 +1,107 @@
+namespace FastReport.Data
+{
+ partial class GoogleSheetsConnectionEditor
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ this.gbSelect = new System.Windows.Forms.GroupBox();
+ this.btSignInGoogle = new System.Windows.Forms.Button();
+ this.cbxFieldNames = new System.Windows.Forms.CheckBox();
+ this.tbGoogleId = new System.Windows.Forms.TextBox();
+ this.lblSelectGId = new System.Windows.Forms.Label();
+ this.gbSelect.SuspendLayout();
+ this.SuspendLayout();
+ //
+ // gbSelect
+ //
+ this.gbSelect.Controls.Add(this.btSignInGoogle);
+ this.gbSelect.Controls.Add(this.cbxFieldNames);
+ this.gbSelect.Controls.Add(this.tbGoogleId);
+ this.gbSelect.Controls.Add(this.lblSelectGId);
+ this.gbSelect.Location = new System.Drawing.Point(8, 4);
+ this.gbSelect.Name = "gbSelect";
+ this.gbSelect.Size = new System.Drawing.Size(320, 135);
+ this.gbSelect.TabIndex = 1;
+ this.gbSelect.TabStop = false;
+ this.gbSelect.Text = "Select database file";
+ //
+ // btSignInGoogle
+ //
+ this.btSignInGoogle.Location = new System.Drawing.Point(239, 106);
+ this.btSignInGoogle.Name = "btSignInGoogle";
+ this.btSignInGoogle.Size = new System.Drawing.Size(75, 23);
+ this.btSignInGoogle.TabIndex = 8;
+ this.btSignInGoogle.Text = "Sign in";
+ this.btSignInGoogle.UseVisualStyleBackColor = true;
+ this.btSignInGoogle.Click += new System.EventHandler(this.btSignInGoogle_Click);
+ //
+ // cbxFieldNames
+ //
+ this.cbxFieldNames.AutoSize = true;
+ this.cbxFieldNames.Location = new System.Drawing.Point(15, 73);
+ this.cbxFieldNames.Name = "cbxFieldNames";
+ this.cbxFieldNames.Size = new System.Drawing.Size(174, 20);
+ this.cbxFieldNames.TabIndex = 7;
+ this.cbxFieldNames.Text = "Field names in first string";
+ this.cbxFieldNames.UseVisualStyleBackColor = true;
+ //
+ // tbGoogleId
+ //
+ this.tbGoogleId.Location = new System.Drawing.Point(15, 36);
+ this.tbGoogleId.Name = "tbGoogleId";
+ this.tbGoogleId.Size = new System.Drawing.Size(299, 22);
+ this.tbGoogleId.TabIndex = 1;
+ //
+ // lblSelectGId
+ //
+ this.lblSelectGId.AutoSize = true;
+ this.lblSelectGId.Location = new System.Drawing.Point(12, 20);
+ this.lblSelectGId.Name = "lblSelectGId";
+ this.lblSelectGId.Size = new System.Drawing.Size(102, 16);
+ this.lblSelectGId.TabIndex = 0;
+ this.lblSelectGId.Text = "Select sheetsId:";
+ //
+ // GoogleSheetsConnectionEditor
+ //
+ this.Controls.Add(this.gbSelect);
+ this.Name = "GoogleSheetsConnectionEditor";
+ this.Size = new System.Drawing.Size(336, 145);
+ this.gbSelect.ResumeLayout(false);
+ this.gbSelect.PerformLayout();
+ this.ResumeLayout(false);
+
+ }
+
+ #endregion
+
+ private System.Windows.Forms.GroupBox gbSelect;
+ private System.Windows.Forms.Label lblSelectGId;
+ private System.Windows.Forms.TextBox tbGoogleId;
+ private System.Windows.Forms.CheckBox cbxFieldNames;
+ private System.Windows.Forms.Button btSignInGoogle;
+ }
+}
diff --git a/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/GoogleSheetsConnectionEditor.resx b/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/GoogleSheetsConnectionEditor.resx
new file mode 100644
index 00000000..d58980a3
--- /dev/null
+++ b/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/GoogleSheetsConnectionEditor.resx
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/GoogleSheetsConnectionStringBuilder.cs b/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/GoogleSheetsConnectionStringBuilder.cs
new file mode 100644
index 00000000..f4426b4b
--- /dev/null
+++ b/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/GoogleSheetsConnectionStringBuilder.cs
@@ -0,0 +1,127 @@
+using System.Data.Common;
+
+namespace FastReport.Data
+{
+ ///
+ /// Represents the GoogleDataConnection connection string builder.
+ ///
+ ///
+ /// Use this class to parse connection string returned by the GoogleDataConnection class.
+ ///
+ public class GoogleSheetsConnectionStringBuilder : DbConnectionStringBuilder
+ {
+ #region Properties
+
+ ///
+ /// Gets or sets id Google Sheets.
+ ///
+ public string Sheets
+ {
+ get
+ {
+ object gSheets;
+ if (TryGetValue("GoogleSheets", out gSheets))
+ {
+ return (string)gSheets;
+ }
+ return "";
+ }
+
+ set { base["GoogleSheets"] = value; }
+ }
+
+ ///
+ /// Gets or sets table name.
+ ///
+ public string TableName
+ {
+ get
+ {
+ object tableName;
+ if (TryGetValue("TableName", out tableName))
+ {
+ return (string)tableName;
+ }
+ return "";
+ }
+
+ set { base["TableName"] = value; }
+ }
+
+ ///
+ /// Gets or sets first column in the Google Sheets.
+ ///
+ public string StartColumn
+ {
+ get
+ {
+ object startColumn;
+ if (TryGetValue("StartColumn", out startColumn))
+ {
+ return (string)startColumn;
+ }
+ return "";
+ }
+
+ set { base["StartColumn"] = value; }
+ }
+
+ ///
+ /// Gets or sets last column in the Google Sheets.
+ ///
+ public string EndColumn
+ {
+ get
+ {
+ object endColumn;
+ if (TryGetValue("EndColumn", out endColumn))
+ {
+ return (string)endColumn;
+ }
+ return "";
+ }
+
+ set { base["EndColumn"] = value; }
+ }
+
+ ///
+ /// Gets or sets the value indicating that field names should be loaded from the first string of the file.
+ ///
+ public bool FieldNamesInFirstString
+ {
+ get
+ {
+ object fieldNamesInFirstString;
+ if (TryGetValue("FieldNamesInFirstString", out fieldNamesInFirstString))
+ {
+ return fieldNamesInFirstString.ToString().ToLower() == "true";
+ }
+ return false;
+ }
+ set { base["FieldNamesInFirstString"] = value.ToString().ToLower(); }
+ }
+
+ #endregion Properties
+
+ #region Constructors
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public GoogleSheetsConnectionStringBuilder()
+ {
+ ConnectionString = "";
+ }
+
+ ///
+ /// Initializes a new instance of the class with a specified connection string.
+ ///
+ /// The connection string.
+ public GoogleSheetsConnectionStringBuilder(string connectionString) : base()
+ {
+ ConnectionString = connectionString;
+ }
+
+ #endregion Constructors
+ }
+}
diff --git a/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/GoogleSheetsDataConnection.DesignExt.cs b/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/GoogleSheetsDataConnection.DesignExt.cs
new file mode 100644
index 00000000..cc736ec9
--- /dev/null
+++ b/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/GoogleSheetsDataConnection.DesignExt.cs
@@ -0,0 +1,34 @@
+using FastReport.Data.ConnectionEditors;
+using System.Data;
+using System;
+
+namespace FastReport.Data
+{
+ public partial class GoogleSheetsDataConnection
+ {
+ #region Public Methods
+
+ ///
+ public override void TestConnection()
+ {
+ using (DataSet dataset = CreateDataSet())
+ {
+
+ }
+ }
+
+ ///
+ public override ConnectionEditorBase GetEditor()
+ {
+ return new GoogleSheetsConnectionEditor();
+ }
+
+ ///
+ public override string GetConnectionId()
+ {
+ return "GoogleSheets: " + Sheets;
+ }
+
+ #endregion Public Methods
+ }
+}
diff --git a/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/GoogleSheetsDataConnection.cs b/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/GoogleSheetsDataConnection.cs
new file mode 100644
index 00000000..1b9b25fc
--- /dev/null
+++ b/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/GoogleSheetsDataConnection.cs
@@ -0,0 +1,199 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data.Common;
+using System.Data;
+using Google.Apis.Sheets.v4.Data;
+
+namespace FastReport.Data
+{
+ public partial class GoogleSheetsDataConnection : DataConnectionBase
+ {
+ #region Properties
+ ///
+ /// Gets or sets id Google Sheets.
+ ///
+ [Category("Data")]
+ public string Sheets
+ {
+ get
+ {
+ GoogleSheetsConnectionStringBuilder builder = new GoogleSheetsConnectionStringBuilder(ConnectionString);
+ return builder.Sheets;
+ }
+ set
+ {
+ GoogleSheetsConnectionStringBuilder builder = new GoogleSheetsConnectionStringBuilder(ConnectionString);
+ builder.Sheets = value;
+ ConnectionString = builder.ToString();
+ }
+ }
+
+ ///
+ /// Gets or sets table name.
+ ///
+ [Category("Data")]
+ public string TableName
+ {
+ get
+ {
+ GoogleSheetsConnectionStringBuilder builder = new GoogleSheetsConnectionStringBuilder(ConnectionString);
+ return builder.TableName;
+ }
+ set
+ {
+ GoogleSheetsConnectionStringBuilder builder = new GoogleSheetsConnectionStringBuilder(ConnectionString);
+ builder.TableName = value;
+ ConnectionString = builder.ToString();
+ }
+ }
+
+ ///
+ /// Gets or sets first column in the Google Sheets.
+ ///
+ [Category("Data")]
+ public string StartColumn
+ {
+ get
+ {
+ GoogleSheetsConnectionStringBuilder builder = new GoogleSheetsConnectionStringBuilder(ConnectionString);
+ return builder.StartColumn;
+ }
+ set
+ {
+ GoogleSheetsConnectionStringBuilder builder = new GoogleSheetsConnectionStringBuilder(ConnectionString);
+ builder.StartColumn = value;
+ ConnectionString = builder.ToString();
+ }
+ }
+
+ ///
+ /// Gets or sets last column in the Google Sheets.
+ ///
+ public string EndColumn
+ {
+ get
+ {
+ GoogleSheetsConnectionStringBuilder builder = new GoogleSheetsConnectionStringBuilder(ConnectionString);
+ return builder.EndColumn;
+ }
+ set
+ {
+ GoogleSheetsConnectionStringBuilder builder = new GoogleSheetsConnectionStringBuilder(ConnectionString);
+ builder.EndColumn = value;
+ ConnectionString = builder.ToString();
+ }
+ }
+
+ ///
+ /// Gets or sets the value indicating that field names should be loaded from the first string of the file.
+ ///
+ public bool FieldNamesInFirstString
+ {
+ get
+ {
+ GoogleSheetsConnectionStringBuilder builder = new GoogleSheetsConnectionStringBuilder(ConnectionString);
+ return builder.FieldNamesInFirstString;
+ }
+ set
+ {
+ GoogleSheetsConnectionStringBuilder builder = new GoogleSheetsConnectionStringBuilder(ConnectionString);
+ builder.FieldNamesInFirstString = value;
+ ConnectionString = builder.ToString();
+ }
+ }
+
+ #endregion Properties
+
+ #region Constructors
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public GoogleSheetsDataConnection()
+ {
+ IsSqlBased = false;
+ }
+
+ #endregion Constructors
+
+ #region Protected Methods
+
+ ///
+ protected override DataSet CreateDataSet()
+ {
+ DataSet dataset = base.CreateDataSet();
+ GoogleSheetsConnectionStringBuilder builder = new GoogleSheetsConnectionStringBuilder(ConnectionString);
+ Spreadsheet spreadsheet = GoogleSheetsUtils.ReadTable(builder);
+
+ for (int i = 0; i < spreadsheet.Sheets.Count; i++)
+ {
+ builder.TableName = spreadsheet.Sheets[i].Properties.Title;
+ DataTable table = GoogleSheetsUtils.CreateDataTable(spreadsheet.SpreadsheetId, builder);
+ dataset.Tables.Add(table);
+ }
+
+ return dataset;
+ }
+
+ ///
+ protected override void SetConnectionString(string value)
+ {
+ DisposeDataSet();
+ base.SetConnectionString(value);
+ }
+
+ #endregion Protected Methods
+
+ #region Public Methods
+
+ ///
+ public override void FillTableSchema(DataTable table, string selectCommand, CommandParameterCollection parameters)
+ {
+ // do nothing
+ }
+
+ ///
+ public override void FillTableData(DataTable table, string selectCommand, CommandParameterCollection parameters)
+ {
+ // do nothing
+ }
+
+ ///
+ public override void CreateTable(TableDataSource source)
+ {
+ if (DataSet.Tables.Contains(source.TableName))
+ {
+ source.Table = DataSet.Tables[source.TableName];
+ base.CreateTable(source);
+ }
+ else
+ {
+ source.Table = null;
+ }
+ }
+
+ ///
+ public override void DeleteTable(TableDataSource source)
+ {
+ // do nothing
+ }
+
+ ///
+ public override string QuoteIdentifier(string value, DbConnection connection)
+ {
+ return value;
+ }
+
+ ///
+ public override string[] GetTableNames()
+ {
+ string[] result = new string[DataSet.Tables.Count];
+ for (int i = 0; i < DataSet.Tables.Count; i++)
+ {
+ result[i] = DataSet.Tables[i].TableName;
+ }
+ return result;
+ }
+ #endregion Public Methods
+ }
+}
diff --git a/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/GoogleSheetsUtils.cs b/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/GoogleSheetsUtils.cs
new file mode 100644
index 00000000..c977640a
--- /dev/null
+++ b/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/GoogleSheetsUtils.cs
@@ -0,0 +1,125 @@
+using FastReport.Utils;
+using Google.Apis.Sheets.v4.Data;
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+
+namespace FastReport.Data
+{
+ internal static class GoogleSheetsUtils
+ {
+ ///
+ /// The default field name.
+ ///
+ private const string DEFAULT_FIELD_NAME = "Field";
+
+ ///
+ /// Maximum speaker range.
+ ///
+ private const string ALL_RANGE = "!A:ZZZ";
+
+ internal static Spreadsheet ReadTable(GoogleSheetsConnectionStringBuilder builder)
+ {
+ XmlItem xi = Config.Root.FindItem("GoogleSheets").FindItem("StorageSettings");
+ string id = xi.GetProp("ClientId");
+ string secret = xi.GetProp("ClientSecret");
+ string pathToJson = xi.GetProp("PathToJson");
+ string apiKey = xi.GetProp("ApiKey");
+
+ // checking for empty account data
+ if (!String.IsNullOrEmpty(apiKey))
+ GoogleSheets.InitService(apiKey);
+ else if (!String.IsNullOrEmpty(pathToJson))
+ GoogleSheets.InitService(GoogleAuthService.GetAccessToken(pathToJson));
+ else if (!String.IsNullOrEmpty(id) && !String.IsNullOrEmpty(secret))
+ GoogleSheets.InitService(GoogleAuthService.GetAccessToken(id, secret));
+ else
+ throw new Exception(Res.Get("ConnectionEditors,GoogleSheets,FailedToLogin"));
+
+ // checking for an empty URL string
+ if (String.IsNullOrEmpty(builder.Sheets))
+ throw new Exception(Res.Get("ConnectionEditors,Common,OnlyUrlException"));
+
+ string gId = builder.Sheets;
+
+ if (builder.Sheets.StartsWith("https://docs.google.com/spreadsheets/d/"))
+ {
+ string idGoogle = builder.Sheets.Remove(0, 39);
+ int startIndex = idGoogle.IndexOf("/");
+
+ if (startIndex == -1)
+ throw new Exception(Res.Get("ConnectionEditors,Common,OnlyUrlException"));
+
+ gId = idGoogle.Substring(0, startIndex);
+ }
+
+ var objects = GoogleSheets.ReadSpreadSheet(gId);
+
+ return objects;
+ }
+
+ internal static DataTable CreateDataTable(string spreadSheetsId, GoogleSheetsConnectionStringBuilder builder)
+ {
+ //Receiving a sheet
+ var rawLines = GoogleSheets.ReadData(spreadSheetsId, builder.TableName + ALL_RANGE);
+
+ if (rawLines == null)
+ return null;
+
+ //maxCount is needed to determine the maximum number of cells in the row width
+ int maxCount = rawLines.Max(l => l.Count);
+ DataTable table = new DataTable(builder.TableName);
+
+ //Adding empty columns to align the first row
+ for (int i = 0; i < maxCount; i++)
+ if (rawLines[0].Count < maxCount)
+ rawLines[0].Add("");
+
+ if (builder.FieldNamesInFirstString == true)
+ {
+ foreach (var column in rawLines[0])
+ {
+ table.Columns.Add(column.ToString());
+ }
+ for (int i = 1; i < rawLines.Count; i++)
+ {
+ var row = rawLines[i];
+ var dataRow = table.NewRow();
+
+ for (int j = 0; j < row.Count; j++)
+ {
+ dataRow[j] = row[j].ToString();
+ }
+
+ table.Rows.Add(dataRow);
+ }
+ }
+ else
+ {
+ int count = 0;
+
+ foreach (var column in rawLines[0])
+ {
+ table.Columns.Add(DEFAULT_FIELD_NAME + count.ToString());
+ count++;
+ }
+
+ for (int i = 1; i < rawLines.Count; i++)
+ {
+ var row = rawLines[i];
+ var dataRow = table.NewRow();
+
+ for (int j = 0; j < row.Count; j++)
+ {
+ dataRow[j] = row[j].ToString();
+ }
+
+ table.Rows.Add(dataRow);
+ }
+ }
+
+ return table;
+ }
+ }
+}
diff --git a/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/Shared.props b/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/Shared.props
new file mode 100644
index 00000000..e30cd4ba
--- /dev/null
+++ b/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/Shared.props
@@ -0,0 +1,22 @@
+
+
+ FastReport.Data.GoogleSheets
+ FastReport.Data.GoogleSheets
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ false
+ Never
+
+
+
\ No newline at end of file
diff --git a/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/SignInGoogle.cs b/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/SignInGoogle.cs
new file mode 100644
index 00000000..50225264
--- /dev/null
+++ b/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/SignInGoogle.cs
@@ -0,0 +1,240 @@
+using System;
+using System.Windows.Forms;
+using FastReport.Forms;
+using FastReport.Utils;
+using Google.Apis.Auth.OAuth2;
+
+namespace FastReport.Data
+{
+ internal partial class SignInGoogle : BaseDialogForm
+ {
+ #region Fields
+
+ private string apiKey;
+ private string clientId;
+ private string clientSecret;
+ private string pathToJson;
+ private bool isUserAuthorized;
+
+ #endregion Fields
+
+ #region Properties
+
+ ///
+ /// Gets or sets user's api key.
+ ///
+ public string ApiKey
+ {
+ get { return apiKey; }
+ set { apiKey = value; }
+ }
+
+ ///
+ /// Gets or sets client identifier from the OAuth2 specification.
+ ///
+ public string ClientId
+ {
+ get { return clientId; }
+ set { clientId = value; }
+ }
+
+ ///
+ /// Gets or sets client secret from the OAuth2 specification.
+ ///
+ public string ClientSecret
+ {
+ get { return clientSecret; }
+ set { clientSecret = value; }
+ }
+
+ ///
+ /// Gets or sets client file .json from the OAuth2 specification.
+ ///
+ public string PathToJson
+ {
+ get { return pathToJson; }
+ set { pathToJson = value; }
+ }
+
+ ///
+ /// Gets or sets the information is user authorized or not.
+ ///
+ public bool IsUserAuthorized
+ {
+ get { return isUserAuthorized; }
+ set { isUserAuthorized = value; }
+ }
+
+ #endregion Properties
+
+ #region Constructors
+ public SignInGoogle()
+ {
+ this.clientId = "";
+ this.clientSecret = "";
+ this.pathToJson = "";
+ this.apiKey = "";
+ this.isUserAuthorized = false;
+
+ InitializeComponent();
+ Localize();
+ InitSignInList();
+ UIUtils.CheckRTL(this);
+ UpdateDpiDependencies();
+ }
+
+ #endregion Constructors
+
+ #region Events Handlers
+
+ private void tbPathToJsonFile_ButtonClick(object sender, EventArgs e)
+ {
+ using (OpenFileDialog dialog = new OpenFileDialog())
+ {
+ dialog.Filter = Res.Get("FileFilters,JsonFile");
+ if (dialog.ShowDialog() == DialogResult.OK)
+ tbPathToJsonFile.Text = dialog.FileName;
+ }
+ }
+
+ private void cbxPathToJsonFile_CheckedChanged(object sender, EventArgs e)
+ {
+ if (cbxPathToJsonFile.Checked)
+ {
+ lblPath.Visible = true;
+ lblClientId.Visible = false;
+ lblClientSecret.Visible = false;
+
+ tbPathToJsonFile.Visible = true;
+ tbClientId.Visible = false;
+ tbClientSecret.Visible = false;
+ }
+ else
+ {
+ lblPath.Visible = false;
+ lblClientId.Visible = true;
+ lblClientSecret.Visible = true;
+
+ tbPathToJsonFile.Visible = false;
+ tbClientId.Visible = true;
+ tbClientSecret.Visible = true;
+ }
+ }
+
+ private void btnSignIn_Click(object sender, EventArgs e)
+ {
+ if (!String.IsNullOrEmpty(tbClientId.Text) && !String.IsNullOrEmpty(tbClientSecret.Text))
+ {
+ clientId = tbClientId.Text;
+ clientSecret = tbClientSecret.Text;
+ var token = GoogleAuthService.GetAccessToken(ClientId, ClientSecret);
+ IsUserAuthorized = true;
+ XmlItem xi = Config.Root.FindItem("GoogleSheets").FindItem("StorageSettings");
+ xi.SetProp("ClientId", ClientId);
+ xi.SetProp("ClientSecret", ClientSecret);
+ xi.SetProp("PathToJson", "");
+ xi.SetProp("ApiKey", "");
+ xi.SetProp("IsUserAuthorized", IsUserAuthorized.ToString());
+ xi.SetProp("AccessToken", token.ToString());
+ DialogResult = DialogResult.OK;
+ this.Close();
+ }
+ else if (!String.IsNullOrEmpty(tbPathToJsonFile.Text) && cbxPathToJsonFile.Checked)
+ {
+ pathToJson = tbPathToJsonFile.Text;
+ var token = GoogleAuthService.GetAccessToken(PathToJson);
+ IsUserAuthorized = true;
+ XmlItem xi = Config.Root.FindItem("GoogleSheets").FindItem("StorageSettings");
+ xi.SetProp("ClientId", "");
+ xi.SetProp("ClientSecret", "");
+ xi.SetProp("PathToJson", PathToJson);
+ xi.SetProp("ApiKey", "");
+ xi.SetProp("IsUserAuthorized", IsUserAuthorized.ToString());
+ xi.SetProp("AccessToken", token.ToString());
+ DialogResult = DialogResult.OK;
+ this.Close();
+ }
+ else if (!String.IsNullOrEmpty(tbApiKey.Text))
+ {
+ apiKey = tbApiKey.Text;
+ IsUserAuthorized = true;
+ XmlItem xi = Config.Root.FindItem("GoogleSheets").FindItem("StorageSettings");
+ xi.SetProp("ClientId", "");
+ xi.SetProp("ClientSecret", "");
+ xi.SetProp("PathToJson", "");
+ xi.SetProp("ApiKey", ApiKey);
+ xi.SetProp("IsUserAuthorized", IsUserAuthorized.ToString());
+ DialogResult = DialogResult.OK;
+ this.Close();
+ }
+
+ if (IsUserAuthorized == true)
+ {
+ MessageBox.Show(Res.Get("Forms,SignInGoogle,OnConnection"));
+ }
+ else
+ {
+ MessageBox.Show(Res.Get("Forms,SignInGoogle,OffConnection"));
+ }
+ }
+
+ private void cbxChangeSignIn_SelectedIndexChanged(object sender, EventArgs e)
+ {
+ if (cbxChangeSignIn.SelectedIndex == 0)
+ {
+ gbxSignInGoogleAPI.Visible = true;
+ gbxSignInGoogleAPI.Enabled = true;
+
+ gbxGoogleApiKey.Visible = false;
+ gbxGoogleApiKey.Enabled = false;
+ }
+ else
+ {
+ gbxSignInGoogleAPI.Visible = false;
+ gbxSignInGoogleAPI.Enabled = false;
+
+ gbxGoogleApiKey.Visible = true;
+ gbxGoogleApiKey.Enabled = true;
+ }
+ }
+
+ #endregion Events Handlers
+
+ #region Private Methods
+
+ private void InitSignInList()
+ {
+ cbxChangeSignIn.Items.Add("OAuth 2.0");
+ cbxChangeSignIn.Items.Add("API key");
+ cbxChangeSignIn.SelectedIndex = 0;
+ }
+
+ #endregion Private Methods
+
+ #region Public Methods
+
+ public override void Localize()
+ {
+ base.Localize();
+ MyRes res = new MyRes("Forms,SignInGoogle");
+
+ lblCaption.Text = res.Get("Caption");
+ gbxSignInGoogleAPI.Text = res.Get("SignInGoogleApi");
+ gbxGoogleApiKey.Text = res.Get("SignInApiKey");
+ lblClientId.Text = res.Get("ClientId");
+ lblClientSecret.Text = res.Get("ClientSecret");
+ lblPath.Text = res.Get("PathToFile");
+ lblApiKey.Text = res.Get("ApiKey");
+ cbxPathToJsonFile.Text = res.Get("CheckPathToJson");
+ btnSignIn.Text = res.Get("SignIn");
+ }
+
+ public override void UpdateDpiDependencies()
+ {
+ base.UpdateDpiDependencies();
+ tbPathToJsonFile.Image = this.GetImage(1);
+ }
+
+ #endregion Public Methods
+ }
+}
\ No newline at end of file
diff --git a/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/SignInGoogle.designer.cs b/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/SignInGoogle.designer.cs
new file mode 100644
index 00000000..58be95ab
--- /dev/null
+++ b/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/SignInGoogle.designer.cs
@@ -0,0 +1,263 @@
+namespace FastReport.Data
+{
+ partial class SignInGoogle
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ this.cbxPathToJsonFile = new System.Windows.Forms.CheckBox();
+ this.tbPathToJsonFile = new FastReport.Controls.TextBoxButton();
+ this.lblPath = new System.Windows.Forms.Label();
+ this.lblClientSecret = new System.Windows.Forms.Label();
+ this.tbClientSecret = new System.Windows.Forms.TextBox();
+ this.lblClientId = new System.Windows.Forms.Label();
+ this.tbClientId = new System.Windows.Forms.TextBox();
+ this.lblCaption = new System.Windows.Forms.Label();
+ this.btnSignIn = new System.Windows.Forms.Button();
+ this.gbxSignInGoogleAPI = new System.Windows.Forms.GroupBox();
+ this.gbxGoogleApiKey = new System.Windows.Forms.GroupBox();
+ this.lblApiKey = new System.Windows.Forms.Label();
+ this.tbApiKey = new System.Windows.Forms.TextBox();
+ this.cbxChangeSignIn = new System.Windows.Forms.ComboBox();
+ this.gbxSignInGoogleAPI.SuspendLayout();
+ this.gbxGoogleApiKey.SuspendLayout();
+ this.SuspendLayout();
+ //
+ // btnOk
+ //
+ this.btnOk.Location = new System.Drawing.Point(151, 315);
+ this.btnOk.Margin = new System.Windows.Forms.Padding(4);
+ this.btnOk.Size = new System.Drawing.Size(94, 29);
+ //
+ // btnCancel
+ //
+ this.btnCancel.Location = new System.Drawing.Point(252, 315);
+ this.btnCancel.Margin = new System.Windows.Forms.Padding(4);
+ this.btnCancel.Size = new System.Drawing.Size(94, 29);
+ //
+ // cbxPathToJsonFile
+ //
+ this.cbxPathToJsonFile.AutoSize = true;
+ this.cbxPathToJsonFile.Location = new System.Drawing.Point(21, 126);
+ this.cbxPathToJsonFile.Margin = new System.Windows.Forms.Padding(4);
+ this.cbxPathToJsonFile.Name = "cbxPathToJsonFile";
+ this.cbxPathToJsonFile.Size = new System.Drawing.Size(121, 21);
+ this.cbxPathToJsonFile.TabIndex = 17;
+ this.cbxPathToJsonFile.Text = "Check .json file";
+ this.cbxPathToJsonFile.UseVisualStyleBackColor = true;
+ this.cbxPathToJsonFile.CheckedChanged += new System.EventHandler(this.cbxPathToJsonFile_CheckedChanged);
+ //
+ // tbPathToJsonFile
+ //
+ this.tbPathToJsonFile.ButtonText = "";
+ this.tbPathToJsonFile.Image = null;
+ this.tbPathToJsonFile.Location = new System.Drawing.Point(22, 39);
+ this.tbPathToJsonFile.Margin = new System.Windows.Forms.Padding(4);
+ this.tbPathToJsonFile.Name = "tbPathToJsonFile";
+ this.tbPathToJsonFile.Size = new System.Drawing.Size(291, 26);
+ this.tbPathToJsonFile.TabIndex = 16;
+ this.tbPathToJsonFile.Visible = false;
+ this.tbPathToJsonFile.ButtonClick += new System.EventHandler(this.tbPathToJsonFile_ButtonClick);
+ //
+ // lblPath
+ //
+ this.lblPath.AutoSize = true;
+ this.lblPath.Location = new System.Drawing.Point(18, 20);
+ this.lblPath.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
+ this.lblPath.Name = "lblPath";
+ this.lblPath.Size = new System.Drawing.Size(87, 17);
+ this.lblPath.TabIndex = 14;
+ this.lblPath.Text = "Path to .json";
+ this.lblPath.Visible = false;
+ //
+ // lblClientSecret
+ //
+ this.lblClientSecret.AutoSize = true;
+ this.lblClientSecret.Location = new System.Drawing.Point(19, 74);
+ this.lblClientSecret.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
+ this.lblClientSecret.Name = "lblClientSecret";
+ this.lblClientSecret.Size = new System.Drawing.Size(84, 17);
+ this.lblClientSecret.TabIndex = 12;
+ this.lblClientSecret.Text = "Client Secret";
+ //
+ // tbClientSecret
+ //
+ this.tbClientSecret.Location = new System.Drawing.Point(22, 94);
+ this.tbClientSecret.Margin = new System.Windows.Forms.Padding(4);
+ this.tbClientSecret.Name = "tbClientSecret";
+ this.tbClientSecret.Size = new System.Drawing.Size(290, 24);
+ this.tbClientSecret.TabIndex = 13;
+ this.tbClientSecret.UseSystemPasswordChar = true;
+ //
+ // lblClientId
+ //
+ this.lblClientId.AutoSize = true;
+ this.lblClientId.Location = new System.Drawing.Point(19, 20);
+ this.lblClientId.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
+ this.lblClientId.Name = "lblClientId";
+ this.lblClientId.Size = new System.Drawing.Size(59, 17);
+ this.lblClientId.TabIndex = 10;
+ this.lblClientId.Text = "Client ID";
+ //
+ // tbClientId
+ //
+ this.tbClientId.Location = new System.Drawing.Point(22, 39);
+ this.tbClientId.Margin = new System.Windows.Forms.Padding(4);
+ this.tbClientId.Name = "tbClientId";
+ this.tbClientId.Size = new System.Drawing.Size(290, 24);
+ this.tbClientId.TabIndex = 11;
+ this.tbClientId.UseSystemPasswordChar = true;
+ //
+ // lblCaption
+ //
+ this.lblCaption.AutoSize = true;
+ this.lblCaption.Font = new System.Drawing.Font("Calibri Light", 19.5F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(204)));
+ this.lblCaption.Location = new System.Drawing.Point(15, 11);
+ this.lblCaption.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
+ this.lblCaption.Name = "lblCaption";
+ this.lblCaption.Size = new System.Drawing.Size(253, 40);
+ this.lblCaption.TabIndex = 9;
+ this.lblCaption.Text = "Sign in Google API";
+ //
+ // btnSignIn
+ //
+ this.btnSignIn.Location = new System.Drawing.Point(220, 158);
+ this.btnSignIn.Margin = new System.Windows.Forms.Padding(4);
+ this.btnSignIn.Name = "btnSignIn";
+ this.btnSignIn.RightToLeft = System.Windows.Forms.RightToLeft.No;
+ this.btnSignIn.Size = new System.Drawing.Size(94, 29);
+ this.btnSignIn.TabIndex = 0;
+ this.btnSignIn.Text = "Sign In";
+ this.btnSignIn.UseVisualStyleBackColor = true;
+ this.btnSignIn.Click += new System.EventHandler(this.btnSignIn_Click);
+ //
+ // gbxSignInGoogleAPI
+ //
+ this.gbxSignInGoogleAPI.Controls.Add(this.btnSignIn);
+ this.gbxSignInGoogleAPI.Controls.Add(this.lblClientId);
+ this.gbxSignInGoogleAPI.Controls.Add(this.cbxPathToJsonFile);
+ this.gbxSignInGoogleAPI.Controls.Add(this.tbClientId);
+ this.gbxSignInGoogleAPI.Controls.Add(this.tbClientSecret);
+ this.gbxSignInGoogleAPI.Controls.Add(this.tbPathToJsonFile);
+ this.gbxSignInGoogleAPI.Controls.Add(this.lblClientSecret);
+ this.gbxSignInGoogleAPI.Controls.Add(this.lblPath);
+ this.gbxSignInGoogleAPI.Location = new System.Drawing.Point(15, 114);
+ this.gbxSignInGoogleAPI.Margin = new System.Windows.Forms.Padding(4);
+ this.gbxSignInGoogleAPI.Name = "gbxSignInGoogleAPI";
+ this.gbxSignInGoogleAPI.Padding = new System.Windows.Forms.Padding(4);
+ this.gbxSignInGoogleAPI.Size = new System.Drawing.Size(331, 194);
+ this.gbxSignInGoogleAPI.TabIndex = 19;
+ this.gbxSignInGoogleAPI.TabStop = false;
+ this.gbxSignInGoogleAPI.Text = "Sign in Google API with OAuth 2.0";
+ //
+ // gbxGoogleApiKey
+ //
+ this.gbxGoogleApiKey.Controls.Add(this.lblApiKey);
+ this.gbxGoogleApiKey.Controls.Add(this.tbApiKey);
+ this.gbxGoogleApiKey.Enabled = false;
+ this.gbxGoogleApiKey.Location = new System.Drawing.Point(15, 114);
+ this.gbxGoogleApiKey.Margin = new System.Windows.Forms.Padding(4);
+ this.gbxGoogleApiKey.Name = "gbxGoogleApiKey";
+ this.gbxGoogleApiKey.Padding = new System.Windows.Forms.Padding(4);
+ this.gbxGoogleApiKey.Size = new System.Drawing.Size(331, 152);
+ this.gbxGoogleApiKey.TabIndex = 22;
+ this.gbxGoogleApiKey.TabStop = false;
+ this.gbxGoogleApiKey.Text = "Sign in Google API with API key";
+ this.gbxGoogleApiKey.Visible = false;
+ //
+ // lblApiKey
+ //
+ this.lblApiKey.AutoSize = true;
+ this.lblApiKey.Location = new System.Drawing.Point(19, 20);
+ this.lblApiKey.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
+ this.lblApiKey.Name = "lblApiKey";
+ this.lblApiKey.Size = new System.Drawing.Size(54, 17);
+ this.lblApiKey.TabIndex = 10;
+ this.lblApiKey.Text = "API key";
+ //
+ // tbApiKey
+ //
+ this.tbApiKey.Location = new System.Drawing.Point(22, 45);
+ this.tbApiKey.Margin = new System.Windows.Forms.Padding(4);
+ this.tbApiKey.Name = "tbApiKey";
+ this.tbApiKey.Size = new System.Drawing.Size(290, 24);
+ this.tbApiKey.TabIndex = 11;
+ this.tbApiKey.UseSystemPasswordChar = true;
+ //
+ // cbxChangeSignIn
+ //
+ this.cbxChangeSignIn.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
+ this.cbxChangeSignIn.FormattingEnabled = true;
+ this.cbxChangeSignIn.Location = new System.Drawing.Point(15, 68);
+ this.cbxChangeSignIn.Margin = new System.Windows.Forms.Padding(4);
+ this.cbxChangeSignIn.Name = "cbxChangeSignIn";
+ this.cbxChangeSignIn.Size = new System.Drawing.Size(145, 24);
+ this.cbxChangeSignIn.TabIndex = 18;
+ this.cbxChangeSignIn.SelectedIndexChanged += new System.EventHandler(this.cbxChangeSignIn_SelectedIndexChanged);
+ //
+ // SignInGoogle
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(120F, 120F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
+ this.ClientSize = new System.Drawing.Size(364, 359);
+ this.Controls.Add(this.gbxSignInGoogleAPI);
+ this.Controls.Add(this.cbxChangeSignIn);
+ this.Controls.Add(this.lblCaption);
+ this.Controls.Add(this.gbxGoogleApiKey);
+ this.Margin = new System.Windows.Forms.Padding(4);
+ this.Name = "SignInGoogle";
+ this.Controls.SetChildIndex(this.gbxGoogleApiKey, 0);
+ this.Controls.SetChildIndex(this.lblCaption, 0);
+ this.Controls.SetChildIndex(this.cbxChangeSignIn, 0);
+ this.Controls.SetChildIndex(this.gbxSignInGoogleAPI, 0);
+ this.Controls.SetChildIndex(this.btnOk, 0);
+ this.Controls.SetChildIndex(this.btnCancel, 0);
+ this.gbxSignInGoogleAPI.ResumeLayout(false);
+ this.gbxSignInGoogleAPI.PerformLayout();
+ this.gbxGoogleApiKey.ResumeLayout(false);
+ this.gbxGoogleApiKey.PerformLayout();
+ this.ResumeLayout(false);
+ this.PerformLayout();
+
+ }
+
+ #endregion
+ private System.Windows.Forms.CheckBox cbxPathToJsonFile;
+ private Controls.TextBoxButton tbPathToJsonFile;
+ private System.Windows.Forms.Label lblPath;
+ private System.Windows.Forms.Label lblClientSecret;
+ private System.Windows.Forms.TextBox tbClientSecret;
+ private System.Windows.Forms.Label lblClientId;
+ private System.Windows.Forms.TextBox tbClientId;
+ private System.Windows.Forms.Label lblCaption;
+ private System.Windows.Forms.Button btnSignIn;
+ private System.Windows.Forms.GroupBox gbxSignInGoogleAPI;
+ private System.Windows.Forms.ComboBox cbxChangeSignIn;
+ private System.Windows.Forms.GroupBox gbxGoogleApiKey;
+ private System.Windows.Forms.Label lblApiKey;
+ private System.Windows.Forms.TextBox tbApiKey;
+ }
+}
diff --git a/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/SignInGoogle.resx b/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/SignInGoogle.resx
new file mode 100644
index 00000000..d58980a3
--- /dev/null
+++ b/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/SignInGoogle.resx
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/readme.txt b/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/readme.txt
new file mode 100644
index 00000000..e270c9b7
--- /dev/null
+++ b/Extras/Core/FastReport.Data/FastReport.Data.GoogleSheets/readme.txt
@@ -0,0 +1,12 @@
+How to use it:
+- execute the following code once at the application start:
+FastReport.Utils.RegisteredObjects.AddConnection(typeof(GoogleSheetsDataConnection));
+- you should now be able to create a new Google Sheets data connection from code:
+Report report = new Report();
+report.Load(@"YourReport.frx");
+//...
+GoogleSheetsDataConnection conn = new GoogleSheetsDataConnection();
+conn.ConnectionString = "";
+conn.Sheets = "https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit#gid=0";
+conn.CreateAllTables();
+report.Dictionary.Connections.Add(conn);
\ No newline at end of file
diff --git a/Extras/Core/FastReport.Data/FastReport.Data.Odbc/AssemblyInitializer.cs b/Extras/Core/FastReport.Data/FastReport.Data.Odbc/AssemblyInitializer.cs
new file mode 100644
index 00000000..de7cac21
--- /dev/null
+++ b/Extras/Core/FastReport.Data/FastReport.Data.Odbc/AssemblyInitializer.cs
@@ -0,0 +1,12 @@
+using FastReport.Utils;
+
+namespace FastReport.Data
+{
+ public class OdbcAssemblyInitializer : AssemblyInitializerBase
+ {
+ public OdbcAssemblyInitializer()
+ {
+ RegisteredObjects.AddConnection(typeof(OdbcDataConnection));
+ }
+ }
+}
diff --git a/Extras/Core/FastReport.Data/FastReport.Data.Odbc/FastReport.OpenSource.Data.Odbc.csproj b/Extras/Core/FastReport.Data/FastReport.Data.Odbc/FastReport.OpenSource.Data.Odbc.csproj
new file mode 100644
index 00000000..db5863be
--- /dev/null
+++ b/Extras/Core/FastReport.Data/FastReport.Data.Odbc/FastReport.OpenSource.Data.Odbc.csproj
@@ -0,0 +1,12 @@
+
+
+ netstandard2.0;$(NetFrameworkMinimum)
+ true
+
+
+
+
+
+
+
+
diff --git a/Extras/Core/FastReport.Data/FastReport.Data.Odbc/OdbcDataConnection.cs b/Extras/Core/FastReport.Data/FastReport.Data.Odbc/OdbcDataConnection.cs
new file mode 100644
index 00000000..bdadcbe4
--- /dev/null
+++ b/Extras/Core/FastReport.Data/FastReport.Data.Odbc/OdbcDataConnection.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Data;
+using System.Data.Common;
+using System.Data.Odbc;
+using FastReport.Utils;
+
+namespace FastReport.Data
+{
+ ///
+ /// Represents a connection to any database through ODBC.
+ ///
+ /// This example shows how to add a new connection to the report.
+ ///
+ /// Report report1;
+ /// OdbcDataConnection conn = new OdbcDataConnection();
+ /// conn.ConnectionString = "your_connection_string";
+ /// report1.Dictionary.Connections.Add(conn);
+ /// conn.CreateAllTables();
+ ///
+ ///
+ public partial class OdbcDataConnection : DataConnectionBase
+ {
+ ///
+ protected override string GetConnectionStringWithLoginInfo(string userName, string password)
+ {
+ OdbcConnectionStringBuilder builder = new OdbcConnectionStringBuilder(ConnectionString);
+
+ builder.Remove("uid");
+ builder.Add("uid", userName);
+
+ builder.Remove("pwd");
+ builder.Add("pwd", password);
+
+ return builder.ToString();
+ }
+
+ ///
+ public override string QuoteIdentifier(string value, DbConnection connection)
+ {
+ // already quoted?
+ if (value.EndsWith("\"") || value.EndsWith("]") || value.EndsWith("'") || value.EndsWith("`"))
+ return value;
+
+ // Odbc is universal connection, so we need quoting dependent on used database type
+ using (OdbcCommandBuilder builder = new OdbcCommandBuilder())
+ {
+ return builder.QuoteIdentifier(value, connection as OdbcConnection);
+ }
+ }
+
+ ///
+ public override Type GetConnectionType()
+ {
+ return typeof(OdbcConnection);
+ }
+
+ ///
+ public override DbDataAdapter GetAdapter(string selectCommand, DbConnection connection,
+ CommandParameterCollection parameters)
+ {
+ OdbcDataAdapter adapter = new OdbcDataAdapter(selectCommand, connection as OdbcConnection);
+ foreach (CommandParameter p in parameters)
+ {
+ OdbcParameter parameter = adapter.SelectCommand.Parameters.Add(p.Name, (OdbcType)p.DataType, p.Size);
+ parameter.Value = p.Value;
+ }
+
+ return adapter;
+ }
+
+ ///
+ public override Type GetParameterType()
+ {
+ return typeof(OdbcType);
+ }
+ }
+}
diff --git a/Extras/Core/FastReport.Data/FastReport.Data.Odbc/Shared.props b/Extras/Core/FastReport.Data/FastReport.Data.Odbc/Shared.props
new file mode 100644
index 00000000..ee086584
--- /dev/null
+++ b/Extras/Core/FastReport.Data/FastReport.Data.Odbc/Shared.props
@@ -0,0 +1,21 @@
+
+
+ FastReport.Data.Odbc
+ FastReport.Data.Odbc
+
+
+
+
+
+
+
+
+
+
+
+ true
+ false
+ Never
+
+
+
\ No newline at end of file
diff --git a/Extras/Core/FastReport.Data/FastReport.Data.Odbc/readme.txt b/Extras/Core/FastReport.Data/FastReport.Data.Odbc/readme.txt
new file mode 100644
index 00000000..52e1b669
--- /dev/null
+++ b/Extras/Core/FastReport.Data/FastReport.Data.Odbc/readme.txt
@@ -0,0 +1,11 @@
+How to use it:
+- execute the following code once at the application start:
+FastReport.Utils.RegisteredObjects.AddConnection(typeof(OdbcDataConnection));
+- now you should be able to create a new Odbc data connection from code:
+Report report = new Report();
+report.Load(@"YourReport.frx");
+//...
+OdbcDataConnection conn = new OdbcDataConnection();
+conn.ConnectionString = "your connection string";
+conn.CreateAllTables();
+report.Dictionary.Connections.Add(conn);
\ No newline at end of file
diff --git a/FastReport.Base/Barcode/Barcode2DBase.cs b/FastReport.Base/Barcode/Barcode2DBase.cs
index 3a66dc2e..c2617929 100644
--- a/FastReport.Base/Barcode/Barcode2DBase.cs
+++ b/FastReport.Base/Barcode/Barcode2DBase.cs
@@ -27,7 +27,7 @@ private void DrawBarcode(IGraphics g, float width, float height)
g.FillRectangle(Brushes.White, width / 2 - width / 100f * 4, top / 2 - top / 100 * 1.5f, width / 100f * 8, top / 100 * 3);
g.FillRectangle(Brushes.White, width / 2 - width / 100f * 1.5f, top / 2 - top / 100 * 4, width / 100f * 3, top / 100 * 8);
}
- if (text.StartsWith("ST"))
+ if (showMarker && text.StartsWith("ST"))
{
using (Pen skyBluePen = new Pen(Brushes.Black))
{
diff --git a/FastReport.Base/Barcode/BarcodeAztec.cs b/FastReport.Base/Barcode/BarcodeAztec.cs
index 9be21e9d..9841eb14 100644
--- a/FastReport.Base/Barcode/BarcodeAztec.cs
+++ b/FastReport.Base/Barcode/BarcodeAztec.cs
@@ -35,9 +35,9 @@ public BarcodeAztec()
ErrorCorrectionPercent = 33;
}
- internal override void Initialize(string text, bool showText, int angle, float zoom)
+ internal override void Initialize(string text, bool showText, int angle, float zoom, bool showMarker)
{
- base.Initialize(text, showText, angle, zoom);
+ base.Initialize(text, showText, angle, zoom, showMarker);
matrix = Encoder.encode(System.Text.Encoding.ASCII.GetBytes(text), ErrorCorrectionPercent, 0).Matrix;
}
diff --git a/FastReport.Base/Barcode/BarcodeBase.cs b/FastReport.Base/Barcode/BarcodeBase.cs
index 9083eaef..71aa10be 100644
--- a/FastReport.Base/Barcode/BarcodeBase.cs
+++ b/FastReport.Base/Barcode/BarcodeBase.cs
@@ -16,6 +16,7 @@ public abstract class BarcodeBase
internal int angle;
internal bool showText;
internal float zoom;
+ internal bool showMarker;
private Color color;
private Font font;
@@ -92,6 +93,15 @@ internal virtual void Initialize(string text, bool showText, int angle, float zo
this.zoom = zoom;
}
+ internal virtual void Initialize(string text, bool showText, int angle, float zoom, bool showMarker)
+ {
+ this.text = text;
+ this.showText = showText;
+ this.angle = (angle / 90 * 90) % 360;
+ this.zoom = zoom;
+ this.showMarker = showMarker;
+ }
+
internal virtual SizeF CalcBounds()
{
return SizeF.Empty;
diff --git a/FastReport.Base/Barcode/BarcodeDatamatrix.cs b/FastReport.Base/Barcode/BarcodeDatamatrix.cs
index 5bd93fd1..5066c254 100644
--- a/FastReport.Base/Barcode/BarcodeDatamatrix.cs
+++ b/FastReport.Base/Barcode/BarcodeDatamatrix.cs
@@ -1066,9 +1066,9 @@ internal override void Serialize(FastReport.Utils.FRWriter writer, string prefix
writer.WriteBool(prefix + "AutoEncode", AutoEncode);
}
- internal override void Initialize(string text, bool showText, int angle, float zoom)
+ internal override void Initialize(string text, bool showText, int angle, float zoom, bool showMarker)
{
- base.Initialize(text, showText, angle, zoom);
+ base.Initialize(text, showText, angle, zoom, showMarker);
if (SymbolSize == DatamatrixSymbolSize.Auto)
{
diff --git a/FastReport.Base/Barcode/BarcodeMaxiCode.cs b/FastReport.Base/Barcode/BarcodeMaxiCode.cs
index 99381339..205de100 100644
--- a/FastReport.Base/Barcode/BarcodeMaxiCode.cs
+++ b/FastReport.Base/Barcode/BarcodeMaxiCode.cs
@@ -43,9 +43,9 @@ public BarcodeMaxiCode()
Mode = 4;
}
- internal override void Initialize(string text, bool showText, int angle, float zoom)
+ internal override void Initialize(string text, bool showText, int angle, float zoom, bool showMarker)
{
- base.Initialize(text, showText, angle, zoom);
+ base.Initialize(text, showText, angle, zoom, showMarker);
maxiCodeImpl = new MaxiCodeImpl(base.text);
maxiCodeImpl.setMode(Mode);
diff --git a/FastReport.Base/Barcode/BarcodeObject.cs b/FastReport.Base/Barcode/BarcodeObject.cs
index ee838c6e..32301eea 100644
--- a/FastReport.Base/Barcode/BarcodeObject.cs
+++ b/FastReport.Base/Barcode/BarcodeObject.cs
@@ -75,7 +75,6 @@ public enum Alignment
Right
}
-
#region Fields
private int angle;
private bool autoSize;
@@ -94,6 +93,7 @@ public enum Alignment
private bool asBitmap;
private Alignment horzAlign;
private RectangleF origRect;
+ private bool showMarker;
#endregion
#region Properties
@@ -320,12 +320,24 @@ public bool AsBitmap
get { return asBitmap; }
set { asBitmap = value; }
}
+
+ ///
+ /// Gets or sets values for hiding or showing barcode markers
+ ///
+ [DefaultValue(true)]
+ [Category("Appearance")]
+ public bool ShowMarker
+ {
+ get { return showMarker; }
+ set { showMarker = value; }
+ }
+
#endregion
#region Private Methods
private void SetBarcodeProperties()
{
- barcode.Initialize(Text, ShowText, Angle, Zoom);
+ barcode.Initialize(Text, ShowText, Angle, Zoom, ShowMarker);
}
private void DrawBarcode(FRPaintEventArgs e)
@@ -443,6 +455,7 @@ public override void Assign(Base source)
AllowExpressions = src.AllowExpressions;
AsBitmap = src.AsBitmap;
HorzAlign = src.HorzAlign;
+ ShowMarker = src.ShowMarker;
}
///
@@ -532,6 +545,8 @@ public override void Serialize(FRWriter writer)
writer.WriteBool("AsBitmap", AsBitmap);
if (HorzAlign != c.HorzAlign)
writer.WriteValue("HorzAlign", HorzAlign);
+ if (ShowMarker != c.ShowMarker)
+ writer.WriteValue("ShowMarker", ShowMarker);
Barcode.Serialize(writer, "Barcode.", c.Barcode);
}
@@ -650,6 +665,7 @@ public BarcodeObject()
Expression = "";
Text = Barcode.GetDefaultValue();
ShowText = true;
+ ShowMarker = true;
Padding = new Padding();
Zoom = 1;
HideIfNoData = true;
@@ -658,10 +674,8 @@ public BarcodeObject()
Brackets = "[,]";
SetFlags(Flags.HasSmartTag, true);
}
-
}
-
internal static class Barcodes
{
#if READONLY_STRUCTS
diff --git a/FastReport.Base/Barcode/BarcodePDF417.cs b/FastReport.Base/Barcode/BarcodePDF417.cs
index 10d9bedb..47c3ce78 100644
--- a/FastReport.Base/Barcode/BarcodePDF417.cs
+++ b/FastReport.Base/Barcode/BarcodePDF417.cs
@@ -1496,9 +1496,9 @@ internal override void Serialize(FastReport.Utils.FRWriter writer, string prefix
writer.WriteValue(prefix + "PixelSize", PixelSize);
}
- internal override void Initialize(string text, bool showText, int angle, float zoom)
+ internal override void Initialize(string text, bool showText, int angle, float zoom, bool showMarker)
{
- base.Initialize(text, showText, angle, zoom);
+ base.Initialize(text, showText, angle, zoom, showMarker);
if (String.IsNullOrEmpty(text))
bytes = new byte[0];
else
diff --git a/FastReport.Base/Barcode/BarcodeQR.cs b/FastReport.Base/Barcode/BarcodeQR.cs
index f0ce9438..297030a8 100644
--- a/FastReport.Base/Barcode/BarcodeQR.cs
+++ b/FastReport.Base/Barcode/BarcodeQR.cs
@@ -176,11 +176,13 @@ internal override void Serialize(FastReport.Utils.FRWriter writer, string prefix
writer.WriteValue(prefix + "Encoding", Encoding);
if (c == null || QuietZone != c.QuietZone)
writer.WriteBool(prefix + "QuietZone", QuietZone);
+ if (c == null || showMarker != c.showMarker)
+ writer.WriteBool(prefix + "ShowMarker", showMarker);
}
- internal override void Initialize(string text, bool showText, int angle, float zoom)
+ internal override void Initialize(string text, bool showText, int angle, float zoom, bool showMarker)
{
- base.Initialize(text, showText, angle, zoom);
+ base.Initialize(text, showText, angle, zoom, showMarker);
matrix = QRCodeWriter.encode(base.text, 0, 0, GetErrorCorrectionLevel(), GetEncoding(), QuietZone);
}
diff --git a/FastReport.Base/Export/Html/HTMLExportLayers.cs b/FastReport.Base/Export/Html/HTMLExportLayers.cs
index 9704f976..0f5ddafa 100644
--- a/FastReport.Base/Export/Html/HTMLExportLayers.cs
+++ b/FastReport.Base/Export/Html/HTMLExportLayers.cs
@@ -6,6 +6,7 @@
using System.Windows.Forms;
using FastReport.Export;
using System.ComponentModel;
+using System.Drawing.Text;
namespace FastReport.Export.Html
{
@@ -477,13 +478,17 @@ private string GetLayerPicture(ReportComponentBase obj, out float Width, out flo
{
using (Graphics g = Graphics.FromImage(image))
{
+ var needClear = obj is TextObjectBase
#if MSCHART
- if (obj is TextObjectBase || obj is FastReport.MSChart.MSChartObject)
- g.Clear(GetClearColor(obj));
-#else
- if (obj is TextObjectBase)
- g.Clear(GetClearColor(obj));
+ || obj is MSChart.MSChartObject
#endif
+ || obj is Gauge.GaugeObject;
+
+ if (needClear)
+ {
+ g.Clear(Color.Transparent);
+ g.TextRenderingHint = TextRenderingHint.AntiAlias;
+ }
float Left = Width > 0 ? obj.AbsLeft : obj.AbsLeft + Width;
float Top = Height > 0 ? obj.AbsTop : obj.AbsTop + Height;
@@ -555,42 +560,6 @@ private string GetLayerPicture(ReportComponentBase obj, out float Width, out flo
return result;
}
- // Method to get background color of parent to fix an issue with blurred rotated text. g.Clear(Color.Transparent) - reason.
- private Color GetClearColor(ReportComponentBase obj)
- {
- var color = Color.Transparent;
- ReportComponentBase tempObj = obj;
-
- if (obj.Parent is BandBase && obj.Band.Fill.IsTransparent)
- {
- color = Color.White;
- }
- else if (obj.Parent is BandBase)
- {
- color = obj.Band.FillColor;
- }
- else
- {
- var i = 0;
- while (tempObj != null && tempObj.Fill.IsTransparent && !(tempObj.Parent is BandBase) && i < 10)
- {
- i++;
- tempObj = tempObj.Parent as ReportComponentBase;
-
- if (tempObj != null && !tempObj.Fill.IsTransparent)
- {
- color = tempObj.FillColor;
- break;
- }
-
- if (tempObj?.Parent is BandBase)
- color = Color.White;
- }
- }
-
- return color;
- }
-
private void LayerPicture(FastString Page, ReportComponentBase obj, FastString text)
{
if (pictures)
diff --git a/FastReport.Base/ReportPage.cs b/FastReport.Base/ReportPage.cs
index 70f447e9..7d314159 100644
--- a/FastReport.Base/ReportPage.cs
+++ b/FastReport.Base/ReportPage.cs
@@ -1059,6 +1059,15 @@ public override void Draw(FRPaintEventArgs e)
}
DrawBackground(e, pageRect);
+
+ if (UnlimitedHeight || UnlimitedWidth)
+ {
+ printableRect = new RectangleF(pageRect.Left + LeftMargin * Units.Millimeters,
+ pageRect.Top + TopMargin * Units.Millimeters,
+ pageRect.Width - (LeftMargin + RightMargin) * Units.Millimeters,
+ pageRect.Height - (TopMargin + BottomMargin) * Units.Millimeters);
+ }
+
Border.Draw(e, printableRect);
if (Watermark.Enabled)
{
diff --git a/FastReport.Base/ShapeObject.cs b/FastReport.Base/ShapeObject.cs
index 53096c0e..7053c647 100644
--- a/FastReport.Base/ShapeObject.cs
+++ b/FastReport.Base/ShapeObject.cs
@@ -80,37 +80,18 @@ public float Curve
#endregion
#region Private Methods
-#if MONO
private GraphicsPath GetRoundRectPath(float x, float y, float x1, float y1, float radius)
{
GraphicsPath gp = new GraphicsPath();
if (radius < 1)
radius = 1;
- gp.AddLine(x + radius, y, x1 - radius, y);
- gp.AddArc(x1 - radius - 1, y, radius + 1, radius + 1, 270, 90);
- gp.AddLine(x1, y + radius, x1, y1 - radius);
- gp.AddArc(x1 - radius - 1, y1 - radius - 1, radius + 1, radius + 1, 0, 90);
- gp.AddLine(x1 - radius, y1, x + radius, y1);
- gp.AddArc(x, y1 - radius - 1, radius + 1, radius + 1, 90, 90);
- gp.AddLine(x, y1 - radius, x, y + radius);
+ gp.AddArc(x1 - radius, y, radius, radius, 270, 90);
+ gp.AddArc(x1 - radius, y1 - radius, radius, radius, 0, 90);
+ gp.AddArc(x, y1 - radius, radius, radius, 90, 90);
gp.AddArc(x, y, radius, radius, 180, 90);
gp.CloseFigure();
return gp;
}
-#else
- private GraphicsPath GetRoundRectPath(float x, float y, float x1, float y1, float radius)
- {
- GraphicsPath gp = new GraphicsPath();
- if (radius < 1)
- radius = 1;
- gp.AddArc(x1 - radius - 1, y, radius + 1, radius + 1, 270, 90);
- gp.AddArc(x1 - radius - 1, y1 - radius - 1, radius + 1, radius + 1, 0, 90);
- gp.AddArc(x, y1 - radius - 1, radius + 1, radius + 1, 90, 90);
- gp.AddArc(x, y, radius, radius, 180, 90);
- gp.CloseFigure();
- return gp;
- }
-#endif
#endregion
#region Public Methods
diff --git a/FastReport.OpenSource.sln b/FastReport.OpenSource.sln
index 8b43f20a..d8dc76a4 100644
--- a/FastReport.OpenSource.sln
+++ b/FastReport.OpenSource.sln
@@ -57,6 +57,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{A182
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastReport.OpenSource.Plugins.WebP", "Extras\Core\FastReport.Plugin\FastReport.Plugins.WebP\FastReport.OpenSource.Plugins.WebP.csproj", "{28005ED3-3E5B-443F-A054-C74A45BE3C4C}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastReport.OpenSource.Data.Odbc", "Extras\Core\FastReport.Data\FastReport.Data.Odbc\FastReport.OpenSource.Data.Odbc.csproj", "{7C97A904-620B-49A5-9000-41452DA11BB3}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Academic|Any CPU = Academic|Any CPU
@@ -202,6 +204,12 @@ Global
{28005ED3-3E5B-443F-A054-C74A45BE3C4C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{28005ED3-3E5B-443F-A054-C74A45BE3C4C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{28005ED3-3E5B-443F-A054-C74A45BE3C4C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7C97A904-620B-49A5-9000-41452DA11BB3}.Academic|Any CPU.ActiveCfg = Debug|Any CPU
+ {7C97A904-620B-49A5-9000-41452DA11BB3}.Academic|Any CPU.Build.0 = Debug|Any CPU
+ {7C97A904-620B-49A5-9000-41452DA11BB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7C97A904-620B-49A5-9000-41452DA11BB3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7C97A904-620B-49A5-9000-41452DA11BB3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7C97A904-620B-49A5-9000-41452DA11BB3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -229,6 +237,7 @@ Global
{320FE53B-1775-4DD2-8D2B-AB468B2839FA} = {8E9E75EB-2ABC-4CC1-8AA0-C11DEE54A152}
{A1827297-EAD4-413D-BA8D-811F5FF6125D} = {CCF32DAC-85D9-43D4-A4A7-72626A13D806}
{28005ED3-3E5B-443F-A054-C74A45BE3C4C} = {A1827297-EAD4-413D-BA8D-811F5FF6125D}
+ {7C97A904-620B-49A5-9000-41452DA11BB3} = {898AF8DC-11C3-4640-B60C-5D8CBF2F29CF}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {DFBB1F5B-F03E-4BCB-AFEE-A45A2C4D5BB8}
diff --git a/FastReport/Resources/en.xml b/FastReport/Resources/en.xml
index 13df2ae7..240ba0a3 100644
--- a/FastReport/Resources/en.xml
+++ b/FastReport/Resources/en.xml
@@ -134,6 +134,7 @@
+
@@ -2122,6 +2123,7 @@
+
@@ -2356,6 +2358,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2454,6 +2469,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Localization/Russian.frl b/Localization/Russian.frl
index 7c190c41..acafbe10 100644
--- a/Localization/Russian.frl
+++ b/Localization/Russian.frl
@@ -1,4 +1,4 @@
-
+
@@ -134,6 +134,7 @@
+
@@ -1937,6 +1938,7 @@
+
@@ -2155,6 +2157,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2253,6 +2268,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Pack/BuildScripts/Tasks/BaseTasks.cs b/Pack/BuildScripts/Tasks/BaseTasks.cs
index 3186196a..fb9f47f4 100644
--- a/Pack/BuildScripts/Tasks/BaseTasks.cs
+++ b/Pack/BuildScripts/Tasks/BaseTasks.cs
@@ -37,7 +37,8 @@ partial class Program
"ClickHouse",
"Firebird",
"Excel",
- "Cassandra"
+ "Cassandra",
+ "Odbc",
};
internal string SolutionFile => Path.Combine(solutionDirectory, solutionFilename);