miniSql

34.65 KiB
6136600 » zgc123@gmail.com
11/19/2023 创建
#region License

// Copyright 2005-2019 Paul Kohler (https://github.com/paulkohler/minisqlquery). All rights reserved.
// This source code is made available under the terms of the GNU Lesser General Public License v3.0
// https://github.com/paulkohler/minisqlquery/blob/master/LICENSE
#endregion

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Printing;
using System.IO;
using System.Windows.Forms;
using ICSharpCode.TextEditor;
using ICSharpCode.TextEditor.Document;
using ICSharpCode.TextEditor.Gui.CompletionWindow;
using MiniSqlQuery.Commands;
using MiniSqlQuery.Core;
using MiniSqlQuery.Core.Commands;
using MiniSqlQuery.Properties;
using WeifenLuo.WinFormsUI.Docking;

namespace MiniSqlQuery
{
    /// <summary>The query form.</summary>
    public partial class QueryForm : DockContent, IQueryEditor, IPrintableContent
    {
        /// <summary>The _host window.</summary>
        private readonly IHostWindow _hostWindow;

        /// <summary>The _services.</summary>
        private readonly IApplicationServices _services;

        /// <summary>The _settings.</summary>
        private readonly IApplicationSettings _settings;

        /// <summary>The _sync lock.</summary>
        private static object _syncLock = new object();

        /// <summary>Stores the widths of the columns for this window.</summary>
        private Dictionary<string, int> _columnSizes = new Dictionary<string, int>();

        /// <summary>When tru the grid is being resized on fill, used to avoid overriting column width values.</summary>
        private bool _resizingGrid;

        /// <summary>The _highlighting provider loaded.</summary>
        private bool _highlightingProviderLoaded;

        /// <summary>The _is dirty.</summary>
        private bool _isDirty;

        /// <summary>The _runner.</summary>
        private QueryRunner _runner;

        /// <summary>The status message for this window.</summary>
        private string _status = string.Empty;

        /// <summary>The row count for this window (tab dependent).</summary>
        private int? _rowCount;

        /// <summary>The _text find service.</summary>
        private ITextFindService _textFindService;

        private bool _cleaningTabs;

        TextArea _textArea;
        CodeCompletionWindow _completionWindow;

        /// <summary>Initializes a new instance of the <see cref="QueryForm"/> class.</summary>
        public QueryForm()
        {
            InitializeComponent();

            txtQuery.ContextMenuStrip = contextMenuStripQuery;
            LoadHighlightingProvider();
            txtQuery.Document.DocumentChanged += DocumentDocumentChanged;
            _textArea = txtQuery.ActiveTextAreaControl.TextArea;

            contextMenuStripQuery.Items.Add(CommandControlBuilder.CreateToolStripMenuItem<ExecuteTaskCommand>());
            contextMenuStripQuery.Items.Add(CommandControlBuilder.CreateToolStripMenuItem<CancelTaskCommand>());

            editorContextMenuStrip.Items.Add(CommandControlBuilder.CreateToolStripMenuItem<SaveFileCommand>());
            editorContextMenuStrip.Items.Add(CommandControlBuilder.CreateToolStripMenuItemSeparator());
            editorContextMenuStrip.Items.Add(CommandControlBuilder.CreateToolStripMenuItem<CloseActiveWindowCommand>());
            editorContextMenuStrip.Items.Add(CommandControlBuilder.CreateToolStripMenuItem<CloseAllWindowsCommand>());
            editorContextMenuStrip.Items.Add(CommandControlBuilder.CreateToolStripMenuItem<CopyQueryEditorFileNameCommand>());

            CommandControlBuilder.MonitorMenuItemsOpeningForEnabling(editorContextMenuStrip);
        }

        /// <summary>Initializes a new instance of the <see cref="QueryForm"/> class.</summary>
        /// <param name="services">The services.</param>
        /// <param name="settings">The settings.</param>
        /// <param name="hostWindow">The host window.</param>
        public QueryForm(IApplicationServices services, IApplicationSettings settings, IHostWindow hostWindow)
            : this()
        {
            _services = services;
            _settings = settings;
            _hostWindow = hostWindow;

            var completionProvider = _services.Resolve<ICompletionProvider>();
            if (completionProvider.Enabled)
            {
                _textArea.KeyEventHandler += completionProvider.KeyEventHandlerFired;
            }
        }

        public CodeCompletionWindow CodeCompletionWindow
        {
            get { return _completionWindow; }
            set
            {
                _completionWindow = value;
                if (_completionWindow != null)
                {
                    _completionWindow.Closed += CompletionWindowClosed;
                }
            }
        }

        private void CompletionWindowClosed(object sender, EventArgs e)
        {
            if (_completionWindow != null)
            {
                _completionWindow.Closed -= CompletionWindowClosed;
                _completionWindow.Dispose();
                _completionWindow = null;
            }
        }

        /// <summary>Gets or sets AllText.</summary>
        public string AllText
        {
            get { return txtQuery.Text; }
            set { txtQuery.Text = value; }
        }

        /// <summary>
        /// Gets a reference to the batch of queries.
        /// </summary>
        /// <value>The query batch.</value>
        public QueryBatch Batch
        {
            get { return _runner == null ? null : _runner.Batch; }
        }

        /// <summary>Gets a value indicating whether CanReplaceText.</summary>
        public bool CanReplaceText
        {
            get { return true; }
        }

        /// <summary>Gets or sets CursorColumn.</summary>
        public int CursorColumn
        {
            get { return txtQuery.ActiveTextAreaControl.Caret.Column; }
            set { txtQuery.ActiveTextAreaControl.Caret.Column = value; }
        }

        /// <summary>Gets or sets CursorLine.</summary>
        public int CursorLine
        {
            get { return txtQuery.ActiveTextAreaControl.Caret.Line; }
            set { txtQuery.ActiveTextAreaControl.Caret.Line = value; }
        }

        /// <summary>Gets CursorOffset.</summary>
        public int CursorOffset
        {
            get { return txtQuery.ActiveTextAreaControl.Caret.Offset; }
        }

        /// <summary>Gets EditorControl.</summary>
        public Control EditorControl
        {
            get { return txtQuery; }
        }


        /// <summary>Gets FileFilter.</summary>
        public string FileFilter
        {
            get { return "SQL Files (*.sql)|*.sql|All Files (*.*)|*.*"; }
        }

        /// <summary>Gets or sets FileName.</summary>
        public string FileName
        {
            get { return txtQuery.FileName; }
            set
            {
                txtQuery.FileName = value;

                SetTabTextByFilename();
            }
        }

        /// <summary>Gets a value indicating whether IsBusy.</summary>
        public bool IsBusy { get; private set; }

        /// <summary>Gets or sets a value indicating whether IsDirty.</summary>
        public bool IsDirty
        {
            get { return _isDirty; }
            set
            {
                if (_isDirty != value)
                {
                    _isDirty = value;
                    SetTabTextByFilename();
                }
            }
        }

        /// <summary>Gets PrintDocument.</summary>
        public PrintDocument PrintDocument
        {
            get { return txtQuery.PrintDocument; }
        }

        /// <summary>Gets SelectedText.</summary>
        public string SelectedText
        {
            get { return txtQuery.ActiveTextAreaControl.SelectionManager.SelectedText; }
        }

        /// <summary>Gets TextFindService.</summary>
        public ITextFindService TextFindService
        {
            get
            {
                if (_textFindService == null)
                {
                    _textFindService = _services.Resolve<ITextFindService>();
                }

                return _textFindService;
            }
        }

        /// <summary>Gets TotalLines.</summary>
        public int TotalLines
        {
            get { return txtQuery.Document.TotalNumberOfLines; }
        }

        /// <summary>The execute query.</summary>
        /// <param name="sql">The sql.</param>
        public void ExecuteQuery(string sql)
        {
            if (IsBusy)
            {
                _hostWindow.DisplaySimpleMessageBox(this, "Please wait for the current operation to complete.", "Busy");
                return;
            }

            if (_settings.ConnectionDefinition == null)
            {
                _hostWindow.DisplaySimpleMessageBox(this, "Please select a connection.", "Select a Connection");
                return;
            }

            lock (_syncLock)
            {
                IsBusy = true;
            }

            _runner = QueryRunner.Create(_settings.ProviderFactory, _settings.ConnectionDefinition.ConnectionString, _settings.EnableQueryBatching, _settings.CommandTimeout);
            UseWaitCursor = true;
            queryBackgroundWorker.RunWorkerAsync(sql);
        }

        /// <summary>The load highlighting provider.</summary>
        public void LoadHighlightingProvider()
        {
            if (_highlightingProviderLoaded)
            {
                return;
            }

            // see: http://wiki.sharpdevelop.net/Syntax%20highlighting.ashx
            string dir = Path.GetDirectoryName(GetType().Assembly.Location);
            FileSyntaxModeProvider fsmProvider = new FileSyntaxModeProvider(dir);
            HighlightingManager.Manager.AddSyntaxModeFileProvider(fsmProvider); // Attach to the text editor.
            txtQuery.SetHighlighting("SQL");
            _highlightingProviderLoaded = true;
        }

        /// <summary>The clear selection.</summary>
        public void ClearSelection()
        {
            txtQuery.ActiveTextAreaControl.SelectionManager.ClearSelection();
        }

        /// <summary>The highlight string.</summary>
        /// <param name="offset">The offset.</param>
        /// <param name="length">The length.</param>
        public void HighlightString(int offset, int length)
        {
            if (offset < 0 || length < 1)
            {
                return;
            }

            int endPos = offset + length;
            txtQuery.ActiveTextAreaControl.SelectionManager.SetSelection(
                txtQuery.Document.OffsetToPosition(offset),
                txtQuery.Document.OffsetToPosition(endPos));
            SetCursorByOffset(endPos);
        }

        /// <summary>The insert text.</summary>
        /// <param name="text">The text.</param>
        public void InsertText(string text)
        {
            if (string.IsNullOrEmpty(text))
            {
                return;
            }

            int offset = txtQuery.ActiveTextAreaControl.Caret.Offset;

            // if some text is selected we want to replace it
            if (txtQuery.ActiveTextAreaControl.SelectionManager.IsSelected(offset))
            {
                offset = txtQuery.ActiveTextAreaControl.SelectionManager.SelectionCollection[0].Offset;
                txtQuery.ActiveTextAreaControl.SelectionManager.RemoveSelectedText();
            }

            txtQuery.Document.Insert(offset, text);
            int newOffset = offset + text.Length; // new offset at end of inserted text

            // now reposition the caret if required to be after the inserted text
            if (CursorOffset != newOffset)
            {
                SetCursorByOffset(newOffset);
            }

            txtQuery.Focus();
        }

        /// <summary>The load file.</summary>
        public void LoadFile()
        {
            txtQuery.LoadFile(FileName);
            IsDirty = false;
        }

        /// <summary>The save file.</summary>
        /// <exception cref="InvalidOperationException"></exception>
        public void SaveFile()
        {
            if (FileName == null)
            {
                throw new InvalidOperationException("The 'FileName' cannot be null");
            }

            txtQuery.SaveFile(FileName);
            IsDirty = false;
        }

        /// <summary>The set syntax.</summary>
        /// <param name="name">The name.</param>
        public void SetSyntax(string name)
        {
            LoadHighlightingProvider();
            txtQuery.SetHighlighting(name);
        }


        /// <summary>The find string.</summary>
        /// <param name="value">The value.</param>
        /// <param name="startIndex">The start index.</param>
        /// <param name="comparisonType">The comparison type.</param>
        /// <returns>The find string.</returns>
        public int FindString(string value, int startIndex, StringComparison comparisonType)
        {
            if (string.IsNullOrEmpty(value) || startIndex < 0)
            {
                return -1;
            }

            string text = AllText;
            int pos = text.IndexOf(value, startIndex, comparisonType);
            if (pos > -1)
            {
                ClearSelection();
                HighlightString(pos, value.Length);
            }

            return pos;
        }

        /// <summary>The replace string.</summary>
        /// <param name="value">The value.</param>
        /// <param name="startIndex">The start index.</param>
        /// <param name="length">The length.</param>
        /// <returns>The replace string.</returns>
        public bool ReplaceString(string value, int startIndex, int length)
        {
            if (value == null || startIndex < 0 || length < 0)
            {
                return false;
            }

            if ((startIndex + length) > AllText.Length)
            {
                return false;
            }

            txtQuery.Document.Replace(startIndex, length, value);

            return true;
        }

        /// <summary>The set text find service.</summary>
        /// <param name="textFindService">The text find service.</param>
        public void SetTextFindService(ITextFindService textFindService)
        {
            // accept nulls infering a reset
            _textFindService = textFindService;
        }

        /// <summary>The set cursor by location.</summary>
        /// <param name="line">The line.</param>
        /// <param name="column">The column.</param>
        /// <returns>The set cursor by location.</returns>
        public bool SetCursorByLocation(int line, int column)
        {
            if (line > TotalLines)
            {
                return false;
            }

            txtQuery.ActiveTextAreaControl.Caret.Line = line;
            txtQuery.ActiveTextAreaControl.Caret.Column = column;

            return true;
        }

        /// <summary>The set cursor by offset.</summary>
        /// <param name="offset">The offset.</param>
        /// <returns>The set cursor by offset.</returns>
        public bool SetCursorByOffset(int offset)
        {
            if (offset >= 0)
            {
                txtQuery.ActiveTextAreaControl.Caret.Position = txtQuery.Document.OffsetToPosition(offset);
                return true;
            }

            return false;
        }

        /// <summary>The cancel task.</summary>
        public void CancelTask()
        {
            if (queryBackgroundWorker.IsBusy && _runner != null)
            {
                _runner.Cancel();
            }
        }

        /// <summary>The execute task.</summary>
        public void ExecuteTask()
        {
            if (!string.IsNullOrEmpty(SelectedText))
            {
                ExecuteQuery(SelectedText);
            }
            else
            {
                ExecuteQuery(AllText);
            }
        }

        /// <summary>The set status.</summary>
        /// <param name="text">The text.</param>
        public void SetStatus(string text)
        {
            _status = text;
            UpdateHostStatus();
        }

        public void SetRowCount(int? rows)
        {
            _rowCount = rows;
            UpdateHostStatus();
        }

        /// <summary>The create default font.</summary>
        /// <returns></returns>
        protected Font CreateDefaultFont()
        {
            return new Font("Courier New", 8.25F, FontStyle.Regular, GraphicsUnit.Point);
        }

        /// <summary>The update host status.</summary>
        protected void UpdateHostStatus()
        {
            _hostWindow.SetStatus(this, _status);
            _hostWindow.SetResultCount(this, _rowCount);
        }

        /// <summary>The create query complete message.</summary>
        /// <param name="start">The start.</param>
        /// <param name="end">The end.</param>
        /// <returns>The create query complete message.</returns>
        private static string CreateQueryCompleteMessage(DateTime start, DateTime end)
        {
            TimeSpan ts = end.Subtract(start);
            string msg = string.Format(
                "Query complete, {0:00}:{1:00}.{2:000}",
                ts.Minutes,
                ts.Seconds,
                ts.Milliseconds);
            return msg;
        }

        /// <summary>The add tables.</summary>
        private void AddTables()
        {
            ClearGridsAndTabs();
            SetRowCount(null);

            if (Batch != null)
            {
                string nullText = _settings.NullText;
                int counter = 1;

                _resizingGrid = true;

                foreach (Query query in Batch.Queries)
                {
                    DataSet ds = query.Result;
                    if (ds != null)
                    {
                        foreach (DataTable dt in ds.Tables)
                        {
                            DataGridView grid = new DataGridView();
                            DataGridViewCellStyle cellStyle = new DataGridViewCellStyle();

                            grid.AllowUserToAddRows = false;
                            grid.AllowUserToDeleteRows = false;
                            grid.Dock = DockStyle.Fill;
                            grid.Name = "gridResults_" + counter;
                            grid.ReadOnly = true;
                            grid.DataSource = dt;
                            grid.DataError += GridDataError;
                            grid.DefaultCellStyle = cellStyle;
                            cellStyle.NullValue = nullText;
                            cellStyle.Font = CreateDefaultFont();
                            grid.DataBindingComplete += GridDataBindingComplete;
                            grid.Disposed += GridDisposed;
                            grid.ColumnWidthChanged += OnColumnWidthChanged;


                            TabPage tabPage = new TabPage();
                            tabPage.Controls.Add(grid);
                            tabPage.Name = "tabPageResults_" + counter;
                            tabPage.Padding = new Padding(3);
                            tabPage.Text = string.Format("{0}/Table {1}", ds.DataSetName, counter);
                            tabPage.UseVisualStyleBackColor = false;

                            _resultsTabControl.TabPages.Add(tabPage);

                            // create a reasonable default max width for columns
                            int maxColWidth = Math.Max(grid.ClientSize.Width / 2, 100);

                            // Autosize the columns then change the widths, gleaned from SO - http://stackoverflow.com/a/1031871/276563
                            grid.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.DisplayedCells);
                            for (int i = 0; i < grid.Columns.Count; i++)
                            {
                                int columnWidth = grid.Columns[i].Width;
                                grid.Columns[i].AutoSizeMode = DataGridViewAutoSizeColumnMode.None;

                                string headerText = grid.Columns[i].HeaderText;
                                if (!string.IsNullOrEmpty(headerText) && _columnSizes.ContainsKey(headerText))
                                {
                                    // use the previous column size in case its been adjusted etc
                                    grid.Columns[i].Width = _columnSizes[headerText];
                                }
                                else
                                {
                                    // reset to a the smaller of the 2 sizes, this is mainly for the bigger text columns that throw the size out
                                    grid.Columns[i].Width = Math.Min(columnWidth, maxColWidth);

                                    if (!string.IsNullOrEmpty(headerText))
                                    {
                                        _columnSizes[headerText] = grid.Columns[i].Width;
                                    }
                                }
                            }

                            // set the row count for the first tab for now.
                            if (counter == 1)
                            {
                                SetRowCount(dt.Rows.Count);
                            }

                            counter++;
                        }
                    }
                }

                if (!string.IsNullOrEmpty(Batch.Messages))
                {
                    RichTextBox rtf = new RichTextBox();
                    rtf.Font = CreateDefaultFont();
                    rtf.Dock = DockStyle.Fill;
                    rtf.ScrollBars = RichTextBoxScrollBars.ForcedBoth;
                    rtf.Text = Batch.Messages;

                    TabPage tabPage = new TabPage();
                    tabPage.Controls.Add(rtf);
                    tabPage.Name = "tabPageResults_Messages";
                    tabPage.Padding = new Padding(3);
                    tabPage.Dock = DockStyle.Fill;
                    tabPage.Text = Resources.Messages;
                    tabPage.UseVisualStyleBackColor = false;

                    _resultsTabControl.TabPages.Add(tabPage);
                }

                _resizingGrid = false;
            }
        }

        public void OnColumnWidthChanged(object sender, DataGridViewColumnEventArgs e)
        {
            if (_resizingGrid)
            {
                return;
            }

            string headerText = e.Column.HeaderText;
            if (!string.IsNullOrEmpty(headerText))
            {
                _columnSizes[headerText] = e.Column.Width;
            }
        }

        /// <summary>Iterate backweards through list of tabs disposing grid and removing the tab page.</summary>
        private void ClearGridsAndTabs()
        {
            try
            {
                _cleaningTabs = true;

                for (int i = _resultsTabControl.TabPages.Count - 1; i >= 0; i--)
                {
                    TabPage tabPage = _resultsTabControl.TabPages[i];
                    if (tabPage.Controls.Count > 0)
                    {
                        tabPage.Controls[0].Dispose(); // dispose grid
                    }

                    _resultsTabControl.TabPages.Remove(tabPage);
                    tabPage.Dispose();
                }
            }
            finally
            {
                _cleaningTabs = false;
            }
        }

        private void SetResultCountOnTabSelectedIndexChanged(object sender, EventArgs e)
        {
            if (_cleaningTabs)
            {
                return;
            }

            // get the tab
            var tabPage = _resultsTabControl.TabPages[_resultsTabControl.SelectedIndex];

            // get the grid control, should be first
            var dataGridView = tabPage.Controls[0] as DataGridView;

            // default to blank row count
            int? rows = null;
            if (dataGridView != null)
            {
                var data = dataGridView.DataSource as DataTable;
                if (data != null)
                {
                    rows = data.Rows.Count;
                }
            }
            _rowCount = rows;

            UpdateHostStatus();
        }

        /// <summary>The document document changed.</summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The e.</param>
        private void DocumentDocumentChanged(object sender, DocumentEventArgs e)
        {
            IsDirty = true;
        }

        /// <summary>Change the format style of date time columns. This has to be done post-bind.</summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void GridDataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
        {
            DataGridView grid = sender as DataGridView;
            if (grid == null)
            {
                return;
            }

            DataTable dt = grid.DataSource as DataTable;
            if (dt == null)
            {
                return;
            }

            string nullText = _settings.NullText;
            string dateTimeFormat = _settings.DateTimeFormat;

            for (int i = 0; i < dt.Columns.Count; i++)
            {
                if (dt.Columns[i].DataType == typeof(DateTime))
                {
                    DataGridViewCellStyle dateCellStyle = new DataGridViewCellStyle();
                    dateCellStyle.NullValue = nullText;
                    dateCellStyle.Format = dateTimeFormat;
                    grid.Columns[i].DefaultCellStyle = dateCellStyle;
                }
            }
        }

        /// <summary>The grid data error.</summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The e.</param>
        private void GridDataError(object sender, DataGridViewDataErrorEventArgs e)
        {
            e.ThrowException = false;
        }

        /// <summary>Clean up event subscriptions.</summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void GridDisposed(object sender, EventArgs e)
        {
            DataGridView grid = sender as DataGridView;
            if (grid == null)
            {
                return;
            }

            grid.DataBindingComplete -= GridDataBindingComplete;
            grid.Disposed -= GridDisposed;
            grid.ColumnWidthChanged -= OnColumnWidthChanged;
        }

        /// <summary>The query form_ activated.</summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The e.</param>
        private void QueryForm_Activated(object sender, EventArgs e)
        {
            UpdateHostStatus();
        }

        /// <summary>The query form_ deactivate.</summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The e.</param>
        private void QueryForm_Deactivate(object sender, EventArgs e)
        {
            _hostWindow.SetStatus(this, string.Empty);
        }

        /// <summary>The query form_ form closing.</summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The e.</param>
        private void QueryForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (_isDirty)
            {
                DialogResult saveFile = _hostWindow.DisplayMessageBox(
                    this,
                    "Contents changed, do you want to save the file?\r\n" + TabText, "Save Changes?",
                    MessageBoxButtons.YesNoCancel,
                    MessageBoxIcon.Question,
                    MessageBoxDefaultButton.Button1,
                    0,
                    null,
                    null);

                if (saveFile == DialogResult.Cancel)
                {
                    e.Cancel = true;
                }
                else if (saveFile == DialogResult.Yes)
                {
                    CommandManager.GetCommandInstance<SaveFileCommand>().Execute();
                }
            }
        }

        /// <summary>The query form_ load.</summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The e.</param>
        private void QueryForm_Load(object sender, EventArgs e)
        {
        }

        /// <summary>The runner batch progress.</summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The e.</param>
        private void RunnerBatchProgress(object sender, BatchProgressEventArgs e)
        {
            // push the progress % through to the background worker
            decimal i = Math.Max(1, e.Index);
            decimal count = Math.Max(1, e.Count);
            queryBackgroundWorker.ReportProgress(Convert.ToInt32(i / count * 100m));
        }

        /// <summary>The set tab text by filename.</summary>
        private void SetTabTextByFilename()
        {
            string dirty = string.Empty;
            string text = "Untitled";
            string tabtext;

            if (_isDirty)
            {
                dirty = " *";
            }

            if (txtQuery.FileName != null)
            {
                text = FileName;
                tabtext = Path.GetFileName(FileName);
            }
            else
            {
                text += _settings.GetUntitledDocumentCounter();
                tabtext = text;
            }

            TabText = tabtext + dirty;
            ToolTipText = text + dirty;
        }

        /// <summary>The copy tool strip menu item_ click.</summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The e.</param>
        private void copyToolStripMenuItem_Click(object sender, EventArgs e)
        {
            CopyForm win = null;

            try
            {
                DataGridView grid = (DataGridView)_resultsTabControl.SelectedTab.Controls[0];

                if (grid.SelectedCells.Count == 0)
                {
                    return;
                }

                win = new CopyForm();

                if (win.ShowDialog() == DialogResult.Cancel)
                {
                    return;
                }

                SortedList headers = new SortedList();
                SortedList rows = new SortedList();

                string delimiter = win.Delimiter;
                string line = string.Empty;

                for (int i = 0; i < grid.SelectedCells.Count; i++)
                {
                    DataGridViewCell cell = grid.SelectedCells[i];
                    DataGridViewColumn col = cell.OwningColumn;

                    if (!headers.ContainsKey(col.Index))
                    {
                        headers.Add(col.Index, col.Name);
                    }

                    if (!rows.ContainsKey(cell.RowIndex))
                    {
                        rows.Add(cell.RowIndex, cell.RowIndex);
                    }
                }

                if (win.IncludeHeaders)
                {
                    for (int i = 0; i < headers.Count; i++)
                    {
                        line += (string)headers.GetByIndex(i);
                        if (i != headers.Count)
                        {
                            line += delimiter;
                        }
                    }

                    line += "\r\n";
                }

                for (int i = 0; i < rows.Count; i++)
                {
                    DataGridViewRow row = grid.Rows[(int)rows.GetKey(i)];
                    DataGridViewCellCollection cells = row.Cells;

                    for (int j = 0; j < headers.Count; j++)
                    {
                        DataGridViewCell cell = cells[(int)headers.GetKey(j)];

                        if (cell.Selected)
                        {
                            line += cell.Value;
                        }

                        if (j != (headers.Count - 1))
                        {
                            line += delimiter;
                        }
                    }

                    line += "\r\n";
                }

                if (!string.IsNullOrEmpty(line))
                {
                    Clipboard.Clear();
                    Clipboard.SetText(line);

                    _hostWindow.SetStatus(this, "Selected data has been copied to your clipboard");
                }
            }
            finally
            {
                if (win != null)
                {
                    win.Dispose();
                }
            }
        }

        /// <summary>The query background worker_ do work.</summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The e.</param>
        private void queryBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            string sql = (string)e.Argument;
            _runner.BatchProgress += RunnerBatchProgress;
            _runner.ExecuteQuery(sql);
        }

        /// <summary>The query background worker_ progress changed.</summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The e.</param>
        private void queryBackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            SetStatus(string.Format("Processing batch {0}%...", e.ProgressPercentage));
        }

        /// <summary>The query background worker_ run worker completed.</summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The e.</param>
        private void queryBackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            try
            {
                _runner.BatchProgress -= RunnerBatchProgress;
                if (e.Error != null)
                {
                    // todo: improve!
                    _hostWindow.DisplaySimpleMessageBox(this, e.Error.Message, "Error");
                    SetStatus(e.Error.Message);
                }
                else
                {
                    _hostWindow.SetPointerState(Cursors.Default);
                    string message = CreateQueryCompleteMessage(_runner.Batch.StartTime, _runner.Batch.EndTime);
                    if (_runner.Exception != null)
                    {
                        message = "ERROR - " + message;
                    }

                    AddTables();
                    SetStatus(message);
                    txtQuery.Focus();
                }
            }
            finally
            {
                UseWaitCursor = false;
                lock (_syncLock)
                {
                    IsBusy = false;
                }
            }
        }

        /// <summary>The select all tool strip menu item_ click.</summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The e.</param>
        private void selectAllToolStripMenuItem_Click(object sender, EventArgs e)
        {
            DataGridView grid = (DataGridView)_resultsTabControl.SelectedTab.Controls[0];
            grid.SelectAll();
        }
    }
}