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);