Files
HeidiSQL/source/main.pas

9244 lines
315 KiB
ObjectPascal
Raw Blame History

unit Main;
// -------------------------------------
// Main-window
// -------------------------------------
{$I compilers.inc}
interface
uses
Windows, SysUtils, Classes, Graphics, GraphUtil, Forms, Controls, Menus, StdCtrls, Dialogs, Buttons,
Messages, ExtCtrls, ComCtrls, StdActns, ActnList, ImgList, ToolWin, Clipbrd, SynMemo,
SynEdit, SynEditTypes, SynEditKeyCmds, VirtualTrees, DateUtils,
ShlObj, SynEditMiscClasses, SynEditSearch, SynEditRegexSearch, SynCompletionProposal, SynEditHighlighter,
SynHighlighterSQL, Tabs, SynUnicode, SynRegExpr, WideStrUtils, ExtActns,
CommCtrl, Contnrs, Generics.Collections, SynEditExport, SynExportHTML, Math,
routine_editor, trigger_editor, event_editor, options, EditVar, helpers, createdatabase, table_editor,
TableTools, View, Usermanager, SelectDBObject, connections, sqlhelp, mysql_connection,
mysql_api, insertfiles, searchreplace, loaddata, copytable, VTHeaderPopup;
type
TQueryTab = class(TObject)
Number: Integer;
CloseButton: TSpeedButton;
pnlMemo: TPanel;
pnlHelpers: TPanel;
lboxHelpers: TListBox;
HelperListSelectedItems: Array[0..3] of Array of Integer;
tabsetHelpers: TTabSet;
Memo: TSynMemo;
MemoFilename: String;
MemoLineBreaks: TLineBreaks;
spltHelpers: TSplitter;
spltQuery: TSplitter;
LabelResultInfo: TLabel;
Grid: TVirtualStringTree;
TabSheet: TTabSheet;
GridResult: TGridResult;
FilterText: String;
end;
TMainForm = class(TForm)
MainMenu1: TMainMenu;
File1: TMenuItem;
FileNewItem: TMenuItem;
Help1: TMenuItem;
N1: TMenuItem;
FileExitItem: TMenuItem;
menuAbout: TMenuItem;
Edit1: TMenuItem;
CopyItem: TMenuItem;
PasteItem: TMenuItem;
StatusBar: TStatusBar;
ActionList1: TActionList;
actCopy: TAction;
actPaste: TAction;
actNewWindow: TAction;
actExitApplication: TAction;
Extra1: TMenuItem;
FlushUserPrivileges1: TMenuItem;
MenuCopyCSV: TMenuItem;
MenuExport: TMenuItem;
N5: TMenuItem;
MenuImportTextFile: TMenuItem;
Flush1: TMenuItem;
MenuFlushLogs: TMenuItem;
MenuFlushHosts: TMenuItem;
MenuFlushTables: TMenuItem;
MenuFlushTableswithreadlock: TMenuItem;
MenuFlushStatus: TMenuItem;
N6: TMenuItem;
MenuUserManager: TMenuItem;
MenuPreferences: TMenuItem;
N7a: TMenuItem;
menuReadme: TMenuItem;
actUserManager: TAction;
actAboutBox: TAction;
actMaintenance: TAction;
menuMaintenance: TMenuItem;
ImExport1: TMenuItem;
CopyContentsasHTMLTable1: TMenuItem;
actCopyAsHTML: TAction;
actCopyAsCSV: TAction;
menuWebsite: TMenuItem;
N9: TMenuItem;
N11: TMenuItem;
actPrintList: TAction;
actCopyTable: TAction;
ControlBar1: TControlBar;
ToolBarStandard: TToolBar;
ToolButton9: TToolButton;
tlbSep1: TToolButton;
ToolButton5: TToolButton;
ToolButton6: TToolButton;
ToolButton12: TToolButton;
tlbSep2: TToolButton;
ButtonRefresh: TToolButton;
ButtonImportTextfile: TToolButton;
ButtonExport: TToolButton;
ButtonUserManager: TToolButton;
ToolBarData: TToolBar;
actUndo: TEditUndo;
ToolButton14: TToolButton;
actExecuteQuery: TAction;
actExecuteSelection: TAction;
SaveDialog2: TSaveDialog;
ExportSettings1: TMenuItem;
Importsettings1: TMenuItem;
OpenDialog2: TOpenDialog;
menuSupportForum: TMenuItem;
actCopyAsXML: TAction;
actExportData: TAction;
Exportdata1: TMenuItem;
CopyasXMLdata1: TMenuItem;
actExecuteLine: TAction;
actHTMLview: TAction;
actInsertFiles: TAction;
InsertfilesintoBLOBfields1: TMenuItem;
actExportTables: TAction;
actDropObjects: TAction;
actLoadSQL: TAction;
ImportSQL1: TMenuItem;
menuConnections: TPopupMenu;
menuBugtracker: TMenuItem;
menuFeaturetracker: TMenuItem;
menuDownload: TMenuItem;
btnSQLHelp: TToolButton;
menuSQLHelp1: TMenuItem;
N8a: TMenuItem;
Import1: TMenuItem;
tlbSep6: TToolButton;
menuUpdateCheck: TMenuItem;
ImageListMain: TImageList;
actCreateView: TAction;
ToolButton3: TToolButton;
actDataFirst: TAction;
actDataLast: TAction;
actDataInsert: TAction;
actDataDelete: TAction;
actDataPostChanges: TAction;
ToolButton4: TToolButton;
ToolButton7: TToolButton;
ToolButton8: TToolButton;
ToolButton10: TToolButton;
actCreateTable: TAction;
actEmptyTables: TAction;
actCreateDatabase: TAction;
actSQLhelp: TAction;
actRefresh: TAction;
actImportCSV: TAction;
actCut: TAction;
Cut1: TMenuItem;
actExportSettings: TAction;
actImportSettings: TAction;
actSelectTreeBackground: TAction;
actPreferences: TAction;
actFlushHosts: TAction;
actFlushLogs: TAction;
actFlushPrivileges: TAction;
actFlushTables: TAction;
actFlushTableswithreadlock: TAction;
actFlushStatus: TAction;
actUpdateCheck: TAction;
actWebMainsite: TAction;
actWebDownloadpage: TAction;
actWebForum: TAction;
actWebIssuetracker: TAction;
actWebChangelog: TAction;
actReadme: TAction;
actSaveSQL: TAction;
actSaveSQLAs: TAction;
actSaveSQLselection: TAction;
actSaveSQLSnippet: TAction;
actSaveSQLSelectionSnippet: TAction;
actClearQueryEditor: TAction;
actClearFilterEditor: TAction;
actApplyFilter: TAction;
actQueryStopOnErrors: TAction;
actQueryWordWrap: TAction;
actQueryFind: TAction;
actQueryReplace: TAction;
ToolBarQuery: TToolBar;
btnExecuteQuery: TToolButton;
btnLoadSQL: TToolButton;
btnSaveSQL: TToolButton;
btnSaveSQLSnippet: TToolButton;
btnQueryFind: TToolButton;
btnQueryReplace: TToolButton;
btnStopOnErrors: TToolButton;
btnQueryWordwrap: TToolButton;
PopupQueryLoad: TPopupMenu;
actSetDelimiter: TAction;
btnSetDelimiter: TToolButton;
actDataCancelChanges: TAction;
ToolButton1: TToolButton;
actRemoveFilter: TAction;
actCopyAsSQL: TAction;
CopyAsSQLdata: TMenuItem;
panelTop: TPanel;
pnlLeft: TPanel;
DBtree: TVirtualStringTree;
Splitter1: TSplitter;
PageControlMain: TPageControl;
tabData: TTabSheet;
tabDatabase: TTabSheet;
splitterTopBottom: TSplitter;
tabQuery: TTabSheet;
popupDB: TPopupMenu;
menuRefreshDB: TMenuItem;
tabHost: TTabSheet;
PageControlHost: TPageControl;
tabVariables: TTabSheet;
tabProcessList: TTabSheet;
ListVariables: TVirtualStringTree;
ListProcesses: TVirtualStringTree;
popupHost: TPopupMenu;
Kill1: TMenuItem;
ListTables: TVirtualStringTree;
Refresh1: TMenuItem;
pnlDataTop: TPanel;
pnlQueryMemo: TPanel;
SynSQLSyn1: TSynSQLSyn;
SynMemoQuery: TSynMemo;
spltQuery: TSplitter;
OpenDialog1: TOpenDialog;
TimerHostUptime: TTimer;
N5a: TMenuItem;
popupDataGrid: TPopupMenu;
Refresh3: TMenuItem;
popupResultGrid: TPopupMenu;
Copyrecords1: TMenuItem;
CopyasCSVData1: TMenuItem;
N9a: TMenuItem;
LabelResultinfo: TLabel;
TimerConnected: TTimer;
N12: TMenuItem;
popupSqlLog: TPopupMenu;
Clear2: TMenuItem;
Copy1: TMenuItem;
N15: TMenuItem;
N17: TMenuItem;
CopycontentsasHTML1: TMenuItem;
CopycontentsasHTML2: TMenuItem;
Copy3: TMenuItem;
Paste2: TMenuItem;
N4a: TMenuItem;
DataGrid: TVirtualStringTree;
QueryGrid: TVirtualStringTree;
Copytableas1: TMenuItem;
Delete1: TMenuItem;
N6a: TMenuItem;
QF1: TMenuItem;
QF2: TMenuItem;
menuQuickFilter: TMenuItem;
QF3: TMenuItem;
QF4: TMenuItem;
N7: TMenuItem;
DropFilter1: TMenuItem;
PrintList2: TMenuItem;
N1a: TMenuItem;
SynMemoFilter: TSynMemo;
TimerRefresh: TTimer;
Saveastextfile1: TMenuItem;
QF7: TMenuItem;
QF5: TMenuItem;
QF6: TMenuItem;
QF8: TMenuItem;
QF10: TMenuItem;
QF11: TMenuItem;
QF9: TMenuItem;
QF12: TMenuItem;
CopyasXMLdata3: TMenuItem;
CopyasXMLdata2: TMenuItem;
Exportdata3: TMenuItem;
Exportdata2: TMenuItem;
SaveDialogExportData: TSaveDialog;
N11a: TMenuItem;
Copy4: TMenuItem;
N14: TMenuItem;
DataInsertDateTime: TMenuItem;
DataTimestamp: TMenuItem;
DataDateTime: TMenuItem;
DataTime: TMenuItem;
DataDate: TMenuItem;
DataYear: TMenuItem;
ViewasHTML1: TMenuItem;
HTMLview1: TMenuItem;
InsertfilesintoBLOBfields3: TMenuItem;
N19: TMenuItem;
setNULL1: TMenuItem;
menuExporttables: TMenuItem;
popupListHeader: TVTHeaderPopupMenu;
SynCompletionProposal: TSynCompletionProposal;
OpenDialogSQLFile: TOpenDialog;
SaveDialogSQLFile: TSaveDialog;
SynEditSearch1: TSynEditSearch;
SynEditRegexSearch1: TSynEditRegexSearch;
tabCommandStats: TTabSheet;
ListCommandStats: TVirtualStringTree;
QF13: TMenuItem;
QF14: TMenuItem;
QF15: TMenuItem;
QF16: TMenuItem;
QF17: TMenuItem;
QF18: TMenuItem;
QF19: TMenuItem;
N21: TMenuItem;
pnlQueryHelpers: TPanel;
tabsetQueryHelpers: TTabSet;
lboxQueryHelpers: TListBox;
popupQuery: TPopupMenu;
MenuRun: TMenuItem;
MenuRunSelection: TMenuItem;
MenuRunLine: TMenuItem;
MenuItem1: TMenuItem;
menucopy: TMenuItem;
menupaste: TMenuItem;
menuload: TMenuItem;
menusave: TMenuItem;
menuSaveSQL: TMenuItem;
menuclear: TMenuItem;
MenuFind: TMenuItem;
MenuReplace: TMenuItem;
MenuItem2: TMenuItem;
lblDataTop: TLabel;
spltQueryHelpers: TSplitter;
N22: TMenuItem;
N23: TMenuItem;
menuSaveSelectionToFile: TMenuItem;
menuSaveAsSnippet: TMenuItem;
menuSaveSelectionAsSnippet: TMenuItem;
popupQueryHelpers: TPopupMenu;
menuDeleteSnippet: TMenuItem;
menuHelp: TMenuItem;
menuLoadSnippet: TMenuItem;
menuInsertSnippetAtCursor: TMenuItem;
menuExplore: TMenuItem;
menuSQLhelp2: TMenuItem;
menuSQLhelpData: TMenuItem;
menuLogToFile: TMenuItem;
menuOpenLogFolder: TMenuItem;
tabStatus: TTabSheet;
ListStatus: TVirtualStringTree;
Splitter3: TSplitter;
pnlProcessViewBox: TPanel;
pnlProcessView: TPanel;
SynMemoProcessView: TSynMemo;
pnlFilterVT: TPanel;
editFilterVT: TButtonedEdit;
lblFilterVT: TLabel;
lblFilterVTInfo: TLabel;
menuEditVariable: TMenuItem;
menuTreeExpandAll: TMenuItem;
menuTreeCollapseAll: TMenuItem;
tlbDataButtons: TToolBar;
tbtnDataSorting: TToolButton;
tbtnDataColumns: TToolButton;
tbtnDataFilter: TToolButton;
pnlFilter: TPanel;
btnFilterApply: TButton;
lblTableFilter: TLabel;
editFilterSearch: TEdit;
btnFilterClear: TButton;
popupFilter: TPopupMenu;
menuFilterCopy: TMenuItem;
menuFilterPaste: TMenuItem;
N8: TMenuItem;
menuFilterApply: TMenuItem;
menuFilterClear: TMenuItem;
SynMemoSQLLog: TSynMemo;
Insert1: TMenuItem;
Cancelediting1: TMenuItem;
DataPost1: TMenuItem;
menuShowSizeColumn: TMenuItem;
CopygriddataasSQL1: TMenuItem;
CopygriddataasSQL2: TMenuItem;
menuSelectBGColor: TMenuItem;
actPreviousTab: TPreviousTab;
actNextTab: TNextTab;
Nexttab1: TMenuItem;
Previoustab1: TMenuItem;
menuConnectTo: TMenuItem;
actSelectAll: TAction;
actSelectAll1: TMenuItem;
N13: TMenuItem;
ProgressBarStatus: TProgressBar;
menuRecentFilters: TMenuItem;
comboRecentFilters: TComboBox;
lblRecentFilters: TLabel;
Copy2: TMenuItem;
N26: TMenuItem;
actSessionManager: TAction;
Sessionmanager1: TMenuItem;
actCreateRoutine: TAction;
btnExit: TToolButton;
lblSorryNoData: TLabel;
menuPrint: TMenuItem;
menuEditObject: TMenuItem;
menuCreateObject: TMenuItem;
menuDeleteObject: TMenuItem;
menuMaintenance2: TMenuItem;
menuEmptyTables: TMenuItem;
actEditObject: TAction;
menuCreateDB: TMenuItem;
menuCreateTable: TMenuItem;
menuCreateTableCopy: TMenuItem;
menuCreateView: TMenuItem;
menuCreateRoutine: TMenuItem;
tabEditor: TTabSheet;
popupRefresh: TPopupMenu;
menuAutoRefreshSetInterval: TMenuItem;
menuAutoRefresh: TMenuItem;
popupMainTabs: TPopupMenu;
menuNewQueryTab: TMenuItem;
menuCloseTab: TMenuItem;
actNewQueryTab: TAction;
actCloseQueryTab: TAction;
Newquerytab1: TMenuItem;
Closetab1: TMenuItem;
pnlRight: TPanel;
btnCloseFilterPanel: TSpeedButton;
actFilterPanel: TAction;
actFindInVT1: TMenuItem;
TimerFilterVT: TTimer;
actFindTextOnServer: TAction;
actFindTextOnServer1: TMenuItem;
Findtextonserver1: TMenuItem;
actBulkTableEdit: TAction;
menuBulkTableEdit: TMenuItem;
menuQueryHelpersGenerateInsert: TMenuItem;
menuQueryHelpersGenerateUpdate: TMenuItem;
menuQueryHelpersGenerateDelete: TMenuItem;
actCreateTrigger: TAction;
menuCreateTrigger: TMenuItem;
menuQueryCut: TMenuItem;
menuQuerySelectall: TMenuItem;
actDataDuplicateRow: TAction;
Duplicaterow1: TMenuItem;
Bulktableeditor1: TMenuItem;
actSelectInverse: TAction;
Inverseselection1: TMenuItem;
actDataResetSorting: TAction;
Resetsorting1: TMenuItem;
actReformatSQL: TAction;
ReformatSQL1: TMenuItem;
btnReformatSQL: TToolButton;
ReformatSQL2: TMenuItem;
menuQueryInsertFunction: TMenuItem;
menuFilterInsertFunction: TMenuItem;
actBlobAsText: TAction;
btnBlobAsText: TToolButton;
actQueryFindAgain: TAction;
Search1: TMenuItem;
Findtext1: TMenuItem;
actQueryFindAgain1: TMenuItem;
Replacetext1: TMenuItem;
lblExplainProcess: TLabel;
menuExplainProcess: TMenuItem;
ToolButton2: TToolButton;
tbtnDataShowAll: TToolButton;
tbtnDataNext: TToolButton;
actDataShowNext: TAction;
actDataShowAll: TAction;
SynExporterHTML1: TSynExporterHTML;
QFvalues: TMenuItem;
tabDatabases: TTabSheet;
ListDatabases: TVirtualStringTree;
menuFetchDBitems: TMenuItem;
actRunRoutines: TAction;
Runroutines1: TMenuItem;
actCreateEvent: TAction;
Event1: TMenuItem;
procedure actCreateDBObjectExecute(Sender: TObject);
procedure menuConnectionsPopup(Sender: TObject);
procedure actExitApplicationExecute(Sender: TObject);
procedure WMCopyData(var Msg: TWMCopyData); message WM_COPYDATA;
procedure FormDestroy(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure Startup;
procedure DoAfterConnect;
procedure DoDisconnect;
procedure FormResize(Sender: TObject);
procedure actUserManagerExecute(Sender: TObject);
procedure actAboutBoxExecute(Sender: TObject);
procedure actApplyFilterExecute(Sender: TObject);
procedure actClearEditorExecute(Sender: TObject);
procedure actTableToolsExecute(Sender: TObject);
procedure actCopyAsHTMLExecute(Sender: TObject);
procedure actCopyAsCSVExecute(Sender: TObject);
procedure actPrintListExecute(Sender: TObject);
procedure actCopyTableExecute(Sender: TObject);
procedure ShowStatusMsg(Msg: String=''; PanelNr: Integer=6);
function mask(str: String) : String;
procedure actExecuteQueryExecute(Sender: TObject);
procedure actExecuteSelectionExecute(Sender: TObject);
procedure actCopyAsXMLExecute(Sender: TObject);
procedure actCreateDatabaseExecute(Sender: TObject);
procedure actDataCancelChangesExecute(Sender: TObject);
procedure actExportDataExecute(Sender: TObject);
procedure actExecuteLineExecute(Sender: TObject);
procedure actHTMLviewExecute(Sender: TObject);
procedure actInsertFilesExecute(Sender: TObject);
procedure actDataDeleteExecute(Sender: TObject);
procedure actDataFirstExecute(Sender: TObject);
procedure actDataInsertExecute(Sender: TObject);
procedure actDataLastExecute(Sender: TObject);
procedure actDataPostChangesExecute(Sender: TObject);
procedure actDropObjectsExecute(Sender: TObject);
procedure actEmptyTablesExecute(Sender: TObject);
procedure actExportSettingsExecute(Sender: TObject);
procedure actFlushExecute(Sender: TObject);
procedure actImportCSVExecute(Sender: TObject);
procedure actImportSettingsExecute(Sender: TObject);
procedure actLoadSQLExecute(Sender: TObject);
procedure actNewWindowExecute(Sender: TObject);
procedure actSessionManagerExecute(Sender: TObject);
procedure actPreferencesExecute(Sender: TObject);
procedure actQueryFindReplaceExecute(Sender: TObject);
procedure actQueryStopOnErrorsExecute(Sender: TObject);
procedure actQueryWordWrapExecute(Sender: TObject);
procedure actReadmeExecute(Sender: TObject);
procedure actRefreshExecute(Sender: TObject);
procedure actRemoveFilterExecute(Sender: TObject);
procedure actSaveSQLExecute(Sender: TObject);
procedure actSaveSQLAsExecute(Sender: TObject);
procedure actSaveSQLSnippetExecute(Sender: TObject);
procedure actSetDelimiterExecute(Sender: TObject);
procedure actSQLhelpExecute(Sender: TObject);
procedure actUpdateCheckExecute(Sender: TObject);
procedure actWebbrowse(Sender: TObject);
procedure actCopyAsSQLExecute(Sender: TObject);
procedure actSelectTreeBackgroundExecute(Sender: TObject);
procedure popupQueryPopup(Sender: TObject);
procedure lboxQueryHelpersClick(Sender: TObject);
procedure lboxQueryHelpersDblClick(Sender: TObject);
procedure tabsetQueryHelpersChange(Sender: TObject; NewTab: Integer;
var AllowChange: Boolean);
procedure btnDataClick(Sender: TObject);
procedure ListTablesChange(Sender: TBaseVirtualTree; Node: PVirtualNode);
procedure SynCompletionProposalAfterCodeCompletion(Sender: TObject;
const Value: String; Shift: TShiftState; Index: Integer; EndToken: Char);
procedure SynCompletionProposalCodeCompletion(Sender: TObject;
var Value: String; Shift: TShiftState; Index: Integer; EndToken: Char);
procedure SynCompletionProposalExecute(Kind: SynCompletionType;
Sender: TObject; var CurrentInput: String; var x, y: Integer;
var CanExecute: Boolean);
procedure PageControlMainChange(Sender: TObject);
procedure PageControlHostChange(Sender: TObject);
procedure ValidateControls(Sender: TObject);
procedure ValidateQueryControls(Sender: TObject);
procedure RefreshQueryHelpers;
procedure DataGridBeforePaint(Sender: TBaseVirtualTree;
TargetCanvas: TCanvas);
procedure LogSQL(Msg: String; Category: TMySQLLogCategory=lcInfo);
procedure KillProcess(Sender: TObject);
procedure ExecSQLClick(Sender: TObject; Selection: Boolean = false;
CurrentLine: Boolean=false);
procedure SynMemoQueryStatusChange(Sender: TObject; Changes: TSynStatusChanges);
procedure TimerHostUptimeTimer(Sender: TObject);
procedure ListTablesNewText(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex; NewText: String);
procedure TimerConnectedTimer(Sender: TObject);
procedure Clear2Click(Sender: TObject);
procedure QuickFilterClick(Sender: TObject);
procedure AutoRefreshSetInterval(Sender: TObject);
procedure AutoRefreshToggle(Sender: TObject);
procedure SynMemoQueryDragOver(Sender, Source: TObject; X, Y: Integer;
State: TDragState; var Accept: Boolean);
procedure SynMemoQueryDragDrop(Sender, Source: TObject; X, Y: Integer);
procedure SynMemoQueryDropFiles(Sender: TObject; X, Y: Integer; AFiles: TStrings);
procedure popupHostPopup(Sender: TObject);
procedure Saveastextfile1Click(Sender: TObject);
procedure popupDBPopup(Sender: TObject);
procedure SaveDialogExportDataTypeChange(Sender: TObject);
procedure popupDataGridPopup(Sender: TObject);
procedure QFvaluesClick(Sender: TObject);
procedure InsertDate(Sender: TObject);
procedure setNULL1Click(Sender: TObject);
function QueryLoad( filename: String; ReplaceContent: Boolean = true ): Boolean;
procedure DataGridChange(Sender: TBaseVirtualTree; Node: PVirtualNode);
procedure DataGridCreateEditor(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex; out EditLink: IVTEditLink);
procedure DataGridEditCancelled(Sender: TBaseVirtualTree; Column: TColumnIndex);
procedure DataGridEdited(Sender: TBaseVirtualTree; Node: PVirtualNode; Column:
TColumnIndex);
procedure DataGridEditing(Sender: TBaseVirtualTree; Node: PVirtualNode; Column:
TColumnIndex; var Allowed: Boolean);
procedure DataGridFocusChanging(Sender: TBaseVirtualTree; OldNode, NewNode:
PVirtualNode; OldColumn, NewColumn: TColumnIndex; var Allowed: Boolean);
procedure AnyGridGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column:
TColumnIndex; TextType: TVSTTextType; var CellText: String);
procedure DataGridHeaderClick(Sender: TVTHeader; HitInfo: TVTHeaderHitInfo);
procedure AnyGridKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
procedure DataGridNewText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column:
TColumnIndex; NewText: String);
procedure AnyGridPaintText(Sender: TBaseVirtualTree; const TargetCanvas:
TCanvas; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType);
procedure menuDeleteSnippetClick(Sender: TObject);
procedure menuExploreClick(Sender: TObject);
procedure menuInsertSnippetAtCursorClick(Sender: TObject);
procedure menuLoadSnippetClick(Sender: TObject);
procedure vstGetNodeDataSize(Sender: TBaseVirtualTree; var
NodeDataSize: Integer);
procedure vstInitNode(Sender: TBaseVirtualTree; ParentNode, Node:
PVirtualNode; var InitialStates: TVirtualNodeInitStates);
procedure vstFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
procedure vstGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex; TextType: TVSTTextType; var CellText: String);
procedure vstGetImageIndex(Sender: TBaseVirtualTree; Node:
PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex; var Ghosted:
Boolean; var ImageIndex: Integer);
procedure vstHeaderClick(Sender: TVTHeader; HitInfo: TVTHeaderHitInfo);
procedure vstCompareNodes(Sender: TBaseVirtualTree; Node1, Node2:
PVirtualNode; Column: TColumnIndex; var Result: Integer);
procedure vstHeaderDraggedOut(Sender: TVTHeader; Column: TColumnIndex;
DropPosition: TPoint);
procedure vstIncrementalSearch(Sender: TBaseVirtualTree; Node: PVirtualNode; const SearchText: String;
var Result: Integer);
procedure DBtreeFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex);
procedure DBtreeDblClick(Sender: TObject);
procedure DBtreeGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode;
Kind: TVTImageKind; Column: TColumnIndex; var Ghosted: Boolean; var
ImageIndex: Integer);
procedure DBtreeGetNodeDataSize(Sender: TBaseVirtualTree; var NodeDataSize:
Integer);
procedure DBtreeGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column:
TColumnIndex; TextType: TVSTTextType; var CellText: String);
procedure DBtreeInitChildren(Sender: TBaseVirtualTree; Node: PVirtualNode; var
ChildCount: Cardinal);
procedure DBtreeInitNode(Sender: TBaseVirtualTree; ParentNode, Node:
PVirtualNode; var InitialStates: TVirtualNodeInitStates);
procedure DBtreePaintText(Sender: TBaseVirtualTree; const TargetCanvas:
TCanvas; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType);
procedure editFilterSearchChange(Sender: TObject);
procedure editFilterSearchEnter(Sender: TObject);
procedure editFilterSearchExit(Sender: TObject);
procedure menuLogToFileClick(Sender: TObject);
procedure menuOpenLogFolderClick(Sender: TObject);
procedure vstGetHint(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex; var LineBreakStyle: TVTTooltipLineBreakStyle; var
HintText: String);
procedure ListCommandStatsBeforeCellPaint(Sender: TBaseVirtualTree;
TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
CellPaintMode: TVTCellPaintMode; CellRect: TRect; var ContentRect: TRect);
procedure ListTablesBeforeCellPaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas;
Node: PVirtualNode; Column: TColumnIndex; CellPaintMode: TVTCellPaintMode; CellRect: TRect;
var ContentRect: TRect);
procedure ListProcessesBeforeCellPaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas;
Node: PVirtualNode; Column: TColumnIndex; CellPaintMode: TVTCellPaintMode; CellRect: TRect;
var ContentRect: TRect);
procedure ListProcessesFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex);
procedure editFilterVTChange(Sender: TObject);
procedure ListVariablesDblClick(Sender: TObject);
procedure menuEditVariableClick(Sender: TObject);
procedure menuTreeCollapseAllClick(Sender: TObject);
procedure menuTreeExpandAllClick(Sender: TObject);
procedure SynMemoFilterStatusChange(Sender: TObject; Changes: TSynStatusChanges);
procedure DataGridAfterCellPaint(Sender: TBaseVirtualTree;
TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
CellRect: TRect);
procedure menuShowSizeColumnClick(Sender: TObject);
procedure AnyGridBeforeCellPaint(Sender: TBaseVirtualTree;
TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
CellPaintMode: TVTCellPaintMode; CellRect: TRect; var ContentRect: TRect);
procedure QueryGridFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex);
procedure pnlQueryHelpersCanResize(Sender: TObject; var NewWidth,
NewHeight: Integer; var Resize: Boolean);
procedure pnlQueryMemoCanResize(Sender: TObject; var NewWidth,
NewHeight: Integer; var Resize: Boolean);
procedure DataGridMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure File1Click(Sender: TObject);
procedure ListVariablesBeforePaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas);
procedure ListStatusBeforePaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas);
procedure ListProcessesBeforePaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas);
procedure ListCommandStatsBeforePaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas);
procedure ListTablesBeforePaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas);
procedure ListTablesGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode; Kind: TVTImageKind;
Column: TColumnIndex; var Ghosted: Boolean; var ImageIndex: Integer);
procedure ListTablesGetNodeDataSize(Sender: TBaseVirtualTree; var NodeDataSize: Integer);
procedure ListTablesGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
procedure ListTablesInitNode(Sender: TBaseVirtualTree; ParentNode,
Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
procedure vstAfterPaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas);
procedure actCopyOrCutExecute(Sender: TObject);
procedure actPasteExecute(Sender: TObject);
procedure actSelectAllExecute(Sender: TObject);
procedure EnumerateRecentFilters;
procedure LoadRecentFilter(Sender: TObject);
procedure ListTablesEditing(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex; var Allowed: Boolean);
procedure DBtreeChange(Sender: TBaseVirtualTree; Node: PVirtualNode);
procedure actEditObjectExecute(Sender: TObject);
procedure ListTablesDblClick(Sender: TObject);
procedure panelTopDblClick(Sender: TObject);
procedure PageControlMainMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
procedure actNewQueryTabExecute(Sender: TObject);
procedure actCloseQueryTabExecute(Sender: TObject);
procedure menuCloseQueryTab(Sender: TObject);
procedure CloseQueryTab(PageIndex: Integer);
procedure CloseButtonOnMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
procedure CloseButtonOnMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
function GetMainTabAt(X, Y: Integer): Integer;
procedure FixQueryTabCloseButtons;
function ActiveQueryTab: TQueryTab;
function ActiveQueryMemo: TSynMemo;
function ActiveQueryHelpers: TListBox;
function ActiveQueryTabset: TTabset;
function QueryTabActive: Boolean;
function IsQueryTab(PageIndex: Integer; IncludeFixed: Boolean): Boolean;
procedure popupMainTabsPopup(Sender: TObject);
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
procedure actFilterPanelExecute(Sender: TObject);
procedure TimerFilterVTTimer(Sender: TObject);
procedure PageControlMainContextPopup(Sender: TObject; MousePos: TPoint; var Handled: Boolean);
procedure menuQueryHelpersGenerateStatementClick(Sender: TObject);
procedure actDataDuplicateRowExecute(Sender: TObject);
procedure actSelectInverseExecute(Sender: TObject);
procedure FormMouseWheel(Sender: TObject; Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint;
var Handled: Boolean);
procedure actDataResetSortingExecute(Sender: TObject);
procedure actReformatSQLExecute(Sender: TObject);
procedure DBtreeFocusChanging(Sender: TBaseVirtualTree; OldNode,
NewNode: PVirtualNode; OldColumn, NewColumn: TColumnIndex;
var Allowed: Boolean);
procedure actBlobAsTextExecute(Sender: TObject);
procedure SynMemoQueryReplaceText(Sender: TObject; const ASearch,
AReplace: string; Line, Column: Integer; var Action: TSynReplaceAction);
procedure SynMemoQueryPaintTransient(Sender: TObject; Canvas: TCanvas;
TransientType: TTransientType);
procedure actQueryFindAgainExecute(Sender: TObject);
procedure vstScroll(Sender: TBaseVirtualTree; DeltaX, DeltaY: Integer);
procedure lblExplainProcessClick(Sender: TObject);
procedure actDataShowNextExecute(Sender: TObject);
procedure actDataShowAllExecute(Sender: TObject);
procedure AnyGridInitNode(Sender: TBaseVirtualTree; ParentNode,
Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
procedure editFilterVTRightButtonClick(Sender: TObject);
procedure DataGridFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex);
procedure ListTablesKeyPress(Sender: TObject; var Key: Char);
procedure DBtreeFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
procedure ListDatabasesBeforePaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas);
procedure ListDatabasesGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
procedure ListDatabasesInitNode(Sender: TBaseVirtualTree; ParentNode, Node: PVirtualNode;
var InitialStates: TVirtualNodeInitStates);
procedure ListDatabasesGetNodeDataSize(Sender: TBaseVirtualTree; var NodeDataSize: Integer);
procedure ListDatabasesBeforeCellPaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas;
Node: PVirtualNode; Column: TColumnIndex; CellPaintMode: TVTCellPaintMode; CellRect: TRect;
var ContentRect: TRect);
procedure menuFetchDBitemsClick(Sender: TObject);
procedure ListDatabasesGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode;
Kind: TVTImageKind; Column: TColumnIndex; var Ghosted: Boolean; var ImageIndex: Integer);
procedure ListDatabasesDblClick(Sender: TObject);
procedure actRunRoutinesExecute(Sender: TObject);
private
FDelimiter: String;
FileNameSessionLog: String;
FileHandleSessionLog: Textfile;
FLastMouseUpOnPageControl: Cardinal;
FLastTabNumberOnMouseUp: Integer;
FLastMouseDownCloseButton: TObject;
// Filter text per tab for filter panel
FilterTextDatabases,
FilterTextVariables,
FilterTextStatus,
FilterTextProcessList,
FilterTextCommandStats,
FilterTextDatabase,
FilterTextData: String;
PreviousFocusedNode: PVirtualNode;
FProcessDBtreeFocusChanges: Boolean;
FCmdlineFilenames: TStringlist;
FCmdlineConnectionParams: TConnectionParameters;
FCmdlineSessionName: String;
FSearchReplaceExecuted: Boolean;
procedure ParseCommandLineParameters(Parameters: TStringlist);
procedure SetDelimiter(Value: String);
procedure DisplayRowCountStats;
procedure insertFunction(Sender: TObject);
function GetActiveDatabase: String;
function GetSelectedTable: TDBObject;
procedure SetSelectedDatabase(db: String);
procedure ToggleFilterPanel(ForceVisible: Boolean = False);
procedure AutoCalcColWidth(Tree: TVirtualStringTree; Column: TColumnIndex);
procedure PlaceObjectEditor(Obj: TDBObject);
procedure SetTabCaption(PageIndex: Integer; Text: String);
function ConfirmTabClose(PageIndex: Integer): Boolean;
procedure SaveQueryMemo(Tab: TQueryTab; Filename: String; OnlySelection: Boolean);
procedure UpdateFilterPanel(Sender: TObject);
procedure DatabaseChanged(Database: String);
procedure DBObjectsCleared(Database: String);
function GetBlobContent(Results: TMySQLQuery; Column: Integer): String;
procedure DoSearchReplace;
procedure UpdateLineCharPanel;
procedure PaintColorBar(Value, Max: Extended; TargetCanvas: TCanvas; CellRect: TRect);
public
Connection: TMySQLConnection;
SessionName: String;
AllDatabases: TStringList;
AllDatabasesDetails: TMySQLQuery;
btnAddTab: TSpeedButton;
QueryTabs: TObjectList<TQueryTab>;
DBObjectsMaxSize: Int64;
DBObjectsMaxRows: Int64;
ProcessListMaxTime: Int64;
ActiveObjectEditor: TDBObjectEditor;
// Cached forms
TableToolsDialog: TfrmTableTools;
UserManagerForm: TUserManagerForm;
SelectDBObjectForm: TfrmSelectDBObject;
SQLHelpForm: TfrmSQLhelp;
OptionsForm: Toptionsform;
SessionManager: TConnForm;
CreateDatabaseForm: TCreateDatabaseForm;
InsertFiles: TfrmInsertFiles;
EditVariableForm: TfrmEditVariable;
SearchReplaceDialog: TfrmSearchReplace;
ImportTextfileDialog: Tloaddataform;
CopyTableDialog: TCopyTableForm;
// Virtual Tree data arrays
VTRowDataListVariables,
VTRowDataListStatus,
VTRowDataListProcesses,
VTRowDataListCommandStats: TVTreeDataArray;
// Variables set by preferences dialog
prefRememberFilters: Boolean;
prefLogsqlnum: Integer;
prefLogSqlWidth: Integer;
prefDirnameSessionLogs: String;
prefMaxColWidth: Integer;
prefGridRowcountStep: Integer;
prefGridRowcountMax: Integer;
prefGridRowsLineCount: Word;
prefCSVSeparator: String;
prefCSVEncloser: String;
prefCSVTerminator: String;
prefLogToFile: Boolean;
prefLogErrors: Boolean;
prefLogUserSQL: Boolean;
prefLogSQL: Boolean;
prefLogInfos: Boolean;
prefLogDebug: Boolean;
prefEnableBinaryEditor: Boolean;
prefEnableDatetimeEditor: Boolean;
prefEnableEnumEditor: Boolean;
prefEnableSetEditor: Boolean;
prefEnableNullBG: Boolean;
prefExportLocaleNumbers: Boolean;
prefNullColorDefault: TColor;
prefNullBG: TColor;
prefDisplayBars: Boolean;
prefBarColor: TColor;
prefCompletionProposal: Boolean;
// Data grid related stuff
DataGridHiddenColumns: TStringList;
DataGridSortColumns: TOrderColArray;
DataGridWantedRowCount: Int64;
DataGridDB: String;
DataGridTable: String;
DataGridFocusedCell: TStringList;
DataGridFocusedNodeIndex: Int64;
DataGridFocusedColumnName: String;
DataGridHasChanges: Boolean;
DataGridResult: TGridResult;
DataGridFullRowMode: Boolean;
SelectedTableCreateStatement: String;
SelectedTableColumns: TTableColumnList;
SelectedTableKeys: TTableKeyList;
SelectedTableForeignKeys: TForeignKeyList;
FilterPanelManuallyOpened: Boolean;
// Executable file details
AppVerMajor: Integer;
AppVerMinor: Integer;
AppVerRelease: Integer;
AppVerRevision: Integer;
AppVersion: String;
AppDescription: String;
// Common directories
DirnameCommonAppData: String;
DirnameUserAppData: String;
DirnameSnippets: String;
property Delimiter: String read FDelimiter write SetDelimiter;
procedure CallSQLHelpWithKeyword( keyword: String );
procedure AddOrRemoveFromQueryLoadHistory( filename: String;
AddIt: Boolean = true; CheckIfFileExists: Boolean = true );
procedure popupQueryLoadClick( sender: TObject );
procedure FillPopupQueryLoad;
procedure PopupQueryLoadRemoveAbsentFiles( sender: TObject );
procedure SessionConnect(Sender: TObject);
function InitConnection(Params: TConnectionParameters; Session: String): Boolean;
function ActiveGrid: TVirtualStringTree;
function GridResult(Grid: TBaseVirtualTree): TGridResult; overload;
function GridResult(PageIndex: Integer): TGridResult; overload;
property ActiveDatabase : String read GetActiveDatabase write SetSelectedDatabase;
property SelectedTable : TDBObject read GetSelectedTable;
procedure TestVTreeDataArray( P: PVTreeDataArray );
function GetVTreeDataArray( VT: TBaseVirtualTree ): PVTreeDataArray;
procedure ActivateFileLogging;
procedure DeactivateFileLogging;
procedure TrimSQLLog;
function GetTreeNodeType(Tree: TBaseVirtualTree; Node: PVirtualNode): TListNodeType;
function GetFocusedTreeNodeType: TListNodeType;
procedure RefreshTree(DoResetTableCache: Boolean; SelectDatabase: String = '');
procedure RefreshActiveTreeDB(FocusObject: TDBObject);
function FindDBNode(db: String): PVirtualNode;
function GridPostUpdate(Sender: TBaseVirtualTree): Boolean;
function GridPostInsert(Sender: TBaseVirtualTree): Boolean;
function GridPostDelete(Sender: TBaseVirtualTree): Boolean;
function DataGridPostUpdateOrInsert(Node: PVirtualNode): Boolean;
procedure GridFinalizeEditing(Sender: TBaseVirtualTree);
function GetWhereClause(Row: PGridRow; Columns: PGridColumns): String;
function GetKeyColumns: TStringList;
procedure DataGridInsertRow(CopyValuesFromNode: PVirtualNode);
procedure DataGridCancel(Sender: TObject);
procedure CalcNullColors;
procedure HandleDataGridAttributes(RefreshingData: Boolean);
function GetRegKeyTable: String;
procedure SaveListSetup( List: TVirtualStringTree );
procedure RestoreListSetup( List: TVirtualStringTree );
procedure UpdateEditorTab;
procedure SetWindowCaption;
procedure OnMessageHandler(var Msg: TMsg; var Handled: Boolean);
procedure DefaultHandler(var Message); override;
function MaskMulti(str: String): String;
procedure SelectDBObject(Text: String; NodeType: TListNodeType);
procedure SetupSynEditors;
procedure ParseSelectedTableStructure;
function DataGridEnsureFullRow(Grid: TVirtualStringTree; Node: PVirtualNode): Boolean;
procedure DataGridEnsureFullRows(Grid: TVirtualStringTree; SelectedOnly: Boolean);
function DataGridRowHasFullData(Node: PVirtualNode): Boolean;
end;
var
MainForm: TMainForm;
SecondInstMsgId: UINT = 0;
const
// Customized messages
MSG_UPDATECHECK = WM_USER + 1;
MSG_ABOUT = WM_USER + 2;
{$I const.inc}
implementation
uses
About, printlist, mysql_structures, UpdateCheck, uVistaFuncs, runsqlfile,
column_selection, data_sorting, grideditlinks;
{$R *.DFM}
procedure TMainForm.ShowStatusMsg(Msg: String=''; PanelNr: Integer=6);
begin
// Show message in some statusbar panel
StatusBar.Panels[PanelNr].Text := Msg;
StatusBar.Repaint;
end;
procedure TMainForm.actExitApplicationExecute(Sender: TObject);
begin
Close;
end;
procedure TMainForm.actFlushExecute(Sender: TObject);
var
flushwhat: String;
begin
flushwhat := UpperCase(TAction(Sender).Caption);
delete(flushwhat, pos('&', flushwhat), 1);
try
Connection.Query('FLUSH ' + flushwhat);
if Sender = actFlushTableswithreadlock then begin
MessageDlg(
'Tables have been flushed and read lock acquired.'#10 +
'Perform backup or snapshot of table data files now.'#10 +
'Press OK to unlock when done...',
mtInformation, [mbOk], 0
);
Connection.Query('UNLOCK TABLES');
end;
except
on E:Exception do
MessageDlg(E.Message, mtError, [mbOK], 0);
end;
end;
procedure TMainForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
var
i: Integer;
begin
// Prompt on modified changes
CanClose := True;
// Unsaved changes in some query tab?
for i:=0 to QueryTabs.Count-1 do begin
CanClose := ConfirmTabClose(i+tabQuery.PageIndex);
if not CanClose then
Exit;
end;
// Unsaved modified table, trigger, view or routine?
if Assigned(ActiveObjectEditor) then
CanClose := not (ActiveObjectEditor.DeInit in [mrAbort, mrCancel]);
end;
procedure TMainForm.FormDestroy(Sender: TObject);
var
filename, WinState: String;
i: Integer;
begin
// Destroy editors and dialogs. Must be done before connection gets closed, as some destructors do SQL stuff.
FreeAndNil(ActiveObjectEditor);
FreeAndNil(TableToolsDialog);
FreeAndNil(UserManagerForm);
FreeAndNil(SelectDBObjectForm);
FreeAndNil(SQLHelpForm);
FreeAndNil(OptionsForm);
FreeAndNil(SessionManager);
FreeAndNil(CreateDatabaseForm);
FreeAndNil(SearchReplaceDialog);
// Close database connection
DoDisconnect;
// Clearing query and browse data.
SetLength(DataGridResult.Rows, 0);
SetLength(DataGridResult.Columns, 0);
// Save various settings
OpenRegistry;
MainReg.WriteInteger(REGNAME_TOOLBAR2LEFT, ToolBarStandard.Left);
MainReg.WriteInteger(REGNAME_TOOLBAR2TOP, ToolBarStandard.Top);
MainReg.WriteInteger(REGNAME_TOOLBARDATALEFT, ToolBarData.Left);
MainReg.WriteInteger(REGNAME_TOOLBARDATATOP, ToolBarData.Top);
MainReg.WriteInteger(REGNAME_TOOLBARQUERYLEFT, ToolBarQuery.Left);
MainReg.WriteInteger(REGNAME_TOOLBARQUERYTOP, ToolBarQuery.Top);
MainReg.WriteBool(REGNAME_STOPONERRORSINBATCH, actQueryStopOnErrors.Checked);
MainReg.WriteBool(REGNAME_BLOBASTEXT, actBlobAsText.Checked);
MainReg.WriteString( REGNAME_DELIMITER, Delimiter );
MainReg.WriteInteger( REGNAME_QUERYMEMOHEIGHT, pnlQueryMemo.Height );
MainReg.WriteInteger( REGNAME_QUERYHELPERSWIDTH, pnlQueryHelpers.Width );
MainReg.WriteInteger( REGNAME_DBTREEWIDTH, pnlLeft.width );
MainReg.WriteInteger( REGNAME_SQLOUTHEIGHT, SynMemoSQLLog.Height );
MainReg.WriteBool(REGNAME_FILTERACTIVE, pnlFilterVT.Tag=Integer(True));
// Convert set to string.
case WindowState of
wsMinimized: WinState := 'Minimized';
wsMaximized: WinState := 'Maximized';
else WinState := 'Normal';
end;
MainReg.WriteString(REGNAME_WINDOWSTATE, WinState);
// Window dimensions are only valid when WindowState is normal.
if WindowState = wsNormal then begin
MainReg.WriteInteger(REGNAME_WINDOWLEFT, Left);
MainReg.WriteInteger(REGNAME_WINDOWTOP, Top);
MainReg.WriteInteger(REGNAME_WINDOWWIDTH, Width);
MainReg.WriteInteger(REGNAME_WINDOWHEIGHT, Height);
end else begin
// Ensure Left + Top values are at least set to the right monitor area for the next start
i := GetRegValue(REGNAME_WINDOWLEFT, Left);
if (i < Monitor.Left) or (i > Monitor.Left+Monitor.Width) then
MainReg.WriteInteger(REGNAME_WINDOWLEFT, Monitor.Left);
i := GetRegValue(REGNAME_WINDOWTOP, Top);
if (i < Monitor.Top) or (i > Monitor.Top+Monitor.Height) then
MainReg.WriteInteger(REGNAME_WINDOWTOP, Monitor.Top);
end;
SaveListSetup(ListDatabases);
SaveListSetup(ListVariables);
SaveListSetup(ListStatus);
SaveListSetup(ListProcesses);
SaveListSetup(ListCommandStats);
SaveListSetup(ListTables);
filename := GetTempDir+'\'+APPNAME+'-preview.';
if FileExists(filename+'html') then
deletefile(filename+'html');
if FileExists(filename+'jpg') then
deletefile(filename+'jpg');
if FileExists(filename+'gif') then
deletefile(filename+'gif');
if FileExists(filename+'bmp') then
deletefile(filename+'bmp');
if MainReg <> nil then begin
MainReg.CloseKey;
// Export settings into textfile in portable mode.
HandlePortableSettings(False);
MainReg.Free;
end;
end;
{***
OnCreate Event
Important to set the windowstate here instead of in OnShow
because possible windowstate-switching is done with an animation
if set in Windows. This animation takes some milliseconds
to complete and can be annoying.
}
procedure TMainForm.FormCreate(Sender: TObject);
var
i: Integer;
datafontname, WinState: String;
datafontsize : Integer;
DisableProcessWindowsGhostingProc: procedure;
QueryTab: TQueryTab;
Action: TAction;
dwInfoSize, // Size of VERSIONINFO structure
dwVerSize, // Size of Version Info Data
dwWnd: DWORD; // Handle for the size call.
FI: PVSFixedFileInfo; // Delphi structure; see WINDOWS.PAS
ptrVerBuf, Translation, Info: Pointer;
DpiScaleFactor: Double;
begin
caption := APPNAME;
setLocales;
// Detect version
dwInfoSize := GetFileVersionInfoSize(PChar(Application.ExeName), dwWnd);
GetMem(ptrVerBuf, dwInfoSize);
GetFileVersionInfo(PChar(Application.ExeName), dwWnd, dwInfoSize, ptrVerBuf);
VerQueryValue(ptrVerBuf, '\', Pointer(FI), dwVerSize );
AppVerMajor := HiWord(FI.dwFileVersionMS);
AppVerMinor := LoWord(FI.dwFileVersionMS);
AppVerRelease := HiWord(FI.dwFileVersionLS);
AppVerRevision := LoWord(FI.dwFileVersionLS);
AppVersion := Format('%d.%d.%d.%d', [AppVerMajor, AppVerMinor, AppVerRelease, AppVerRevision]);
// Fetch language code and file description
VerQueryValue(ptrVerBuf,'\\VarFileInfo\\Translation', Translation, dwInfoSize);
VerQueryValue(ptrVerBuf,
PChar(Format('\\StringFileInfo\\%.4x%.4x\\%s',
[LoWord(Longint(translation^)), HiWord(Longint(Translation^)), 'FileDescription'])),
Info,
dwInfoSize);
SetString(AppDescription, PChar(Info), dwInfoSize-1);
FreeMem(ptrVerBuf);
// "All users" folder for HeidiSQL's data (All Users\Application Data)
DirnameCommonAppData := GetShellFolder(CSIDL_COMMON_APPDATA) + '\' + APPNAME + '\';
// User folder for HeidiSQL's data (<user name>\Application Data)
DirnameUserAppData := GetShellFolder(CSIDL_APPDATA) + '\' + APPNAME + '\';
// Ensure directory exists
ForceDirectories(DirnameUserAppData);
// Folder which contains snippet-files
DirnameSnippets := DirnameCommonAppData + 'Snippets\';
// SQLFiles-History
FillPopupQueryLoad;
Delimiter := GetRegValue(REGNAME_DELIMITER, DEFAULT_DELIMITER);
// Delphi work around to force usage of Vista's default font (other OSes will be unaffected)
SetVistaFonts(Font);
InheritFont(Font);
InheritFont(tabsetQueryHelpers.Font);
InheritFont(SynCompletionProposal.Font);
// Simulated link label, has non inherited blue font color
InheritFont(lblExplainProcess.Font);
StatusBar.Height := GetTextHeight(StatusBar.Font)+4;
// Upscale panels in non-96-DPI mode
DpiScaleFactor := Screen.PixelsPerInch / FORMS_DPI;
for i:=StatusBar.Panels.Count-1 downto 1 do
StatusBar.Panels[i].Width := Round(StatusBar.Panels[i].Width * DpiScaleFactor);
// Enable auto completion in data tab, filter editor
SynCompletionProposal.AddEditor(SynMemoFilter);
// Fix node height on Virtual Trees for current DPI settings
FixVT(DBTree);
FixVT(ListDatabases);
FixVT(ListVariables);
FixVT(ListStatus);
FixVT(ListProcesses);
FixVT(ListCommandStats);
FixVT(ListTables);
// Window dimensions
Left := GetRegValue(REGNAME_WINDOWLEFT, Left);
Top := GetRegValue(REGNAME_WINDOWTOP, Top);
Width := GetRegValue(REGNAME_WINDOWWIDTH, Width);
Height := GetRegValue(REGNAME_WINDOWHEIGHT, Height);
// Move window to left and/or top edge of monitor, if screen resolution has been decreased
if Left > Monitor.Left+Monitor.Width-100 then
Left := 0;
if Top > Monitor.Top+Monitor.Height-100 then
Top := 0;
WinState := GetRegValue(REGNAME_WINDOWSTATE, '');
if WinState = 'Minimized' then WindowState := wsMinimized else
if WinState = 'Maximized' then WindowState := wsMaximized else
WindowState := wsNormal;
// Position of Toolbars
ToolBarStandard.Left := GetRegValue(REGNAME_TOOLBAR2LEFT, ToolBarStandard.Left);
ToolBarStandard.Top := GetRegValue(REGNAME_TOOLBAR2TOP, ToolBarStandard.Top);
ToolBarData.Left := GetRegValue(REGNAME_TOOLBARDATALEFT, ToolBarData.Left);
ToolBarData.Top := GetRegValue(REGNAME_TOOLBARDATATOP, ToolBarData.Top);
ToolBarQuery.Left := GetRegValue(REGNAME_TOOLBARQUERYLEFT, ToolBarQuery.Left);
ToolBarQuery.Top := GetRegValue(REGNAME_TOOLBARQUERYTOP, ToolBarQuery.Top);
actQueryStopOnErrors.Checked := GetRegValue(REGNAME_STOPONERRORSINBATCH, DEFAULT_STOPONERRORSINBATCH);
actBlobAsText.Checked := GetRegValue(REGNAME_BLOBASTEXT, DEFAULT_BLOBASTEXT);
pnlQueryMemo.Height := GetRegValue(REGNAME_QUERYMEMOHEIGHT, pnlQueryMemo.Height);
pnlQueryHelpers.Width := GetRegValue(REGNAME_QUERYHELPERSWIDTH, pnlQueryHelpers.Width);
pnlLeft.Width := GetRegValue(REGNAME_DBTREEWIDTH, pnlLeft.Width);
SynMemoSQLLog.Height := GetRegValue(REGNAME_SQLOUTHEIGHT, SynMemoSQLLog.Height);
// Force status bar position to below log memo
StatusBar.Top := SynMemoSQLLog.Top + SynMemoSQLLog.Height;
prefMaxColWidth := GetRegValue(REGNAME_MAXCOLWIDTH, DEFAULT_MAXCOLWIDTH);
prefGridRowcountMax := GetRegValue(REGNAME_MAXTOTALROWS, DEFAULT_MAXTOTALROWS);
prefGridRowcountStep := GetRegValue(REGNAME_ROWSPERSTEP, DEFAULT_ROWSPERSTEP);
prefGridRowsLineCount := GetRegValue(REGNAME_GRIDROWSLINECOUNT, DEFAULT_GRIDROWSLINECOUNT);
actDataShowNext.Hint := 'Show next '+FormatNumber(prefGridRowcountStep)+' rows ...';
actAboutBox.Caption := 'About '+APPNAME+' '+AppVersion;
// Fix registry entry from older versions which can have 0 here which makes no sense
// since the autosetting was removed
if prefMaxColWidth <= 0 then
prefMaxColWidth := DEFAULT_MAXCOLWIDTH;
prefLogsqlnum := GetRegValue(REGNAME_LOGSQLNUM, DEFAULT_LOGSQLNUM);
prefLogSqlWidth := GetRegValue(REGNAME_LOGSQLWIDTH, DEFAULT_LOGSQLWIDTH);
prefDirnameSessionLogs := GetRegValue(REGNAME_LOGDIR, DirnameUserAppData + 'Sessionlogs\');
prefCSVSeparator := GetRegValue(REGNAME_CSV_SEPARATOR, DEFAULT_CSV_SEPARATOR);
prefCSVEncloser := GetRegValue(REGNAME_CSV_ENCLOSER, DEFAULT_CSV_ENCLOSER);
prefCSVTerminator := GetRegValue(REGNAME_CSV_TERMINATOR, DEFAULT_CSV_TERMINATOR);
prefExportLocaleNumbers := GetRegValue(REGNAME_EXPORT_LOCALENUMBERS, DEFAULT_EXPORT_LOCALENUMBERS);
prefRememberFilters := GetRegValue(REGNAME_REMEMBERFILTERS, DEFAULT_REMEMBERFILTERS);
prefLogErrors := GetRegValue(REGNAME_LOG_ERRORS, DEFAULT_LOG_ERRORS);
prefLogUserSQL := GetRegValue(REGNAME_LOG_USERSQL, DEFAULT_LOG_USERSQL);
prefLogSQL := GetRegValue(REGNAME_LOG_SQL, DEFAULT_LOG_SQL);
prefLogInfos := GetRegValue(REGNAME_LOG_INFOS, DEFAULT_LOG_INFOS);
prefLogDebug := GetRegValue(REGNAME_LOG_DEBUG, DEFAULT_LOG_DEBUG);
prefDisplayBars := GetRegValue(REGNAME_DISPLAYBARS, DEFAULT_DISPLAYBARS);
prefBarColor := GetRegValue(REGNAME_BARCOLOR, DEFAULT_BARCOLOR);
prefCompletionProposal := GetRegValue(REGNAME_COMPLETIONPROPOSAL, DEFAULT_COMPLETIONPROPOSAL);
// Data-Font:
datafontname := GetRegValue(REGNAME_DATAFONTNAME, DEFAULT_DATAFONTNAME);
datafontsize := GetRegValue(REGNAME_DATAFONTSIZE, DEFAULT_DATAFONTSIZE);
DataGrid.Font.Name := datafontname;
QueryGrid.Font.Name := datafontname;
DataGrid.Font.Size := datafontsize;
QueryGrid.Font.Size := datafontsize;
FixVT(DataGrid, prefGridRowsLineCount);
FixVT(QueryGrid, prefGridRowsLineCount);
// Load color settings
DatatypeCategories[Integer(dtcInteger)].Color := GetRegValue(REGNAME_FIELDCOLOR_NUMERIC, DEFAULT_FIELDCOLOR_NUMERIC);
DatatypeCategories[Integer(dtcReal)].Color := GetRegValue(REGNAME_FIELDCOLOR_NUMERIC, DEFAULT_FIELDCOLOR_NUMERIC);
DatatypeCategories[Integer(dtcText)].Color := GetRegValue(REGNAME_FIELDCOLOR_TEXT, DEFAULT_FIELDCOLOR_TEXT);
DatatypeCategories[Integer(dtcBinary)].Color := GetRegValue(REGNAME_FIELDCOLOR_BINARY, DEFAULT_FIELDCOLOR_BINARY);
DatatypeCategories[Integer(dtcTemporal)].Color := GetRegValue(REGNAME_FIELDCOLOR_DATETIME, DEFAULT_FIELDCOLOR_DATETIME);
DatatypeCategories[Integer(dtcIntegerNamed)].Color := GetRegValue(REGNAME_FIELDCOLOR_ENUM, DEFAULT_FIELDCOLOR_ENUM);
DatatypeCategories[Integer(dtcSet)].Color := GetRegValue(REGNAME_FIELDCOLOR_SET, DEFAULT_FIELDCOLOR_SET);
DatatypeCategories[Integer(dtcSetNamed)].Color := GetRegValue(REGNAME_FIELDCOLOR_SET, DEFAULT_FIELDCOLOR_SET);
prefNullBG := GetRegValue(REGNAME_BG_NULL, DEFAULT_BG_NULL);
CalcNullColors;
// Editor enablings
prefEnableBinaryEditor := GetRegValue(REGNAME_FIELDEDITOR_BINARY, DEFAULT_FIELDEDITOR_BINARY);
prefEnableDatetimeEditor := GetRegValue(REGNAME_FIELDEDITOR_DATETIME, DEFAULT_FIELDEDITOR_DATETIME);
prefEnableEnumEditor := GetRegValue(REGNAME_FIELDEDITOR_ENUM, DEFAULT_FIELDEDITOR_ENUM);
prefEnableSetEditor := GetRegValue(REGNAME_FIELDEDITOR_SET, DEFAULT_FIELDEDITOR_SET);
prefEnableNullBG := GetRegValue(REGNAME_BG_NULL_ENABLED, DEFAULT_BG_NULL_ENABLED);
// Switch off/on displaying table/db sized in tree
menuShowSizeColumn.Checked := GetRegValue(REGNAME_SIZECOL_TREE, DEFAULT_SIZECOL_TREE);
if menuShowSizeColumn.Checked then
DBtree.Header.Columns[1].Options := DBtree.Header.Columns[1].Options + [coVisible]
else
DBtree.Header.Columns[1].Options := DBtree.Header.Columns[1].Options - [coVisible];
// Restore width of columns of all VirtualTrees
RestoreListSetup(ListDatabases);
RestoreListSetup(ListVariables);
RestoreListSetup(ListStatus);
RestoreListSetup(ListProcesses);
RestoreListSetup(ListCommandStats);
RestoreListSetup(ListTables);
// Shortcuts
for i:=0 to ActionList1.ActionCount-1 do begin
Action := TAction(ActionList1.Actions[i]);
Action.ShortCut := GetRegValue(REGPREFIX_SHORTCUT1+Action.Name, Action.ShortCut);
end;
// Place progressbar on the statusbar
ProgressBarStatus.Parent := StatusBar;
ProgressBarStatus.Visible := False;
// Work around Vistas ghosting feature breaking the GUI
DisableProcessWindowsGhostingProc := GetProcAddress(
GetModuleHandle('user32.dll'),
'DisableProcessWindowsGhosting');
if Assigned(DisableProcessWindowsGhostingProc) then
DisableProcessWindowsGhostingProc;
QueryTab := TQueryTab.Create;
QueryTab.TabSheet := tabQuery;
QueryTab.Number := 1;
QueryTab.pnlMemo := pnlQueryMemo;
QueryTab.pnlHelpers := pnlQueryHelpers;
QueryTab.lboxHelpers := lboxQueryHelpers;
QueryTab.tabsetHelpers := tabsetQueryHelpers;
QueryTab.Memo := SynMemoQuery;
QueryTab.MemoLineBreaks := lbsNone;
QueryTab.spltHelpers := spltQueryHelpers;
QueryTab.spltQuery := spltQuery;
QueryTab.LabelResultInfo := LabelResultInfo;
QueryTab.Grid := QueryGrid;
QueryTab.GridResult := TGridResult.Create;
QueryTabs := TObjectList<TQueryTab>.Create;
QueryTabs.Add(QueryTab);
// SynMemo font, hightlighting and shortcuts
SetupSynEditors;
AllDatabases := TStringList.Create;
DataGridResult := TGridResult.Create;
btnAddTab := TSpeedButton.Create(PageControlMain);
btnAddTab.Parent := PageControlMain;
ImageListMain.GetBitmap(actNewQueryTab.ImageIndex, btnAddTab.Glyph);
btnAddTab.Height := PageControlMain.TabRect(0).Bottom - PageControlMain.TabRect(0).Top - 2;
btnAddTab.Width := btnAddTab.Height;
btnAddTab.Flat := True;
btnAddTab.Hint := actNewQueryTab.Hint;
btnAddTab.OnClick := actNewQueryTab.OnExecute;
// Filter panel
ImageListMain.GetBitmap(134, btnCloseFilterPanel.Glyph);
if GetRegValue(REGNAME_FILTERACTIVE, DEFAULT_FILTERACTIVE) then
actFilterPanelExecute(nil);
lblFilterVTInfo.Caption := '';
SelectedTableColumns := TTableColumnList.Create;
SelectedTableKeys := TTableKeyList.Create;
SelectedTableForeignKeys := TForeignKeyList.Create;
FProcessDBtreeFocusChanges := True;
end;
{**
Check for connection parameters on commandline or show connections form.
}
procedure TMainForm.Startup;
var
CmdlineParameters: TStringlist;
LoadedParams: TConnectionParameters;
LastUpdatecheck, LastStatsCall, LastConnect: TDateTime;
UpdatecheckInterval, i: Integer;
DefaultLastrunDate, LastSession, StatsURL: String;
frm : TfrmUpdateCheck;
Connected, DecideForStatistic: Boolean;
StatsCall: TDownloadUrl2;
SessionNames: TStringlist;
DlgResult: TModalResult;
begin
DefaultLastrunDate := '2000-01-01';
// Do an updatecheck if checked in settings
if GetRegValue(REGNAME_DO_UPDATECHECK, DEFAULT_DO_UPDATECHECK) then begin
try
LastUpdatecheck := StrToDateTime( GetRegValue(REGNAME_LAST_UPDATECHECK, DefaultLastrunDate) );
except
LastUpdatecheck := StrToDateTime( DefaultLastrunDate );
end;
UpdatecheckInterval := GetRegValue(REGNAME_UPDATECHECK_INTERVAL, DEFAULT_UPDATECHECK_INTERVAL);
if DaysBetween(Now, LastUpdatecheck) >= UpdatecheckInterval then begin
frm := TfrmUpdateCheck.Create(Self);
frm.AutoClose := True;
frm.CheckForBuildsInAutoMode := GetRegValue(REGNAME_DO_UPDATECHECK_BUILDS, DEFAULT_DO_UPDATECHECK_BUILDS);
frm.ShowModal;
FreeAndNil(frm);
end;
end;
// Call user statistics if checked in settings
if GetRegValue(REGNAME_DO_STATISTICS, DEFAULT_DO_STATISTICS) then begin
try
LastStatsCall := StrToDateTime( GetRegValue(REGNAME_LAST_STATSCALL, DefaultLastrunDate) );
except
LastStatsCall := StrToDateTime( DefaultLastrunDate );
end;
if DaysBetween(Now, LastStatsCall) >= 30 then begin
// Report used SVN revision
StatsURL := APPDOMAIN + 'savestats.php?c=' + IntToStr(AppVerRevision);
// Enumerate actively used server versions
SessionNames := TStringlist.Create;
if MainReg.OpenKey(REGPATH + REGKEY_SESSIONS, true) then
MainReg.GetKeyNames(SessionNames);
for i:=0 to SessionNames.Count-1 do begin
try
LastConnect := StrToDateTime(GetRegValue(REGNAME_LASTCONNECT, DefaultLastrunDate, SessionNames[i]));
except
LastConnect := StrToDateTime(DefaultLastrunDate);
end;
if LastConnect > LastStatsCall then begin
StatsURL := StatsURL + '&s[]=' + IntToStr(GetRegValue(REGNAME_SERVERVERSION, 0, SessionNames[i]));
end;
end;
StatsCall := TDownloadUrl2.Create(Self);
StatsCall.URL := StatsURL;
StatsCall.SetUserAgent(APPNAME + ' ' + AppVersion);
try
StatsCall.ExecuteTarget(nil);
OpenRegistry;
MainReg.WriteString(REGNAME_LAST_STATSCALL, DateTimeToStr(Now));
except
// Silently ignore it when the url could not be called over the network.
end;
FreeAndNil(StatsCall);
end;
end;
// Ask if we shall activate statistic calls. Would be used by noone otherwise.
OpenRegistry;
if not Mainreg.ValueExists(REGNAME_DO_STATISTICS) then begin
DecideForStatistic := MessageDlg(APPNAME + ' has a new statistics feature: If activated, server and client versions '+
'are reported once per month and displayed on heidisql.com.'+CRLF+CRLF+'Activate this feature?',
mtConfirmation, [mbYes, mbNo], 0) = mrYes;
Mainreg.WriteBool(REGNAME_DO_STATISTICS, DecideForStatistic);
end;
Connected := False;
CmdlineParameters := TStringList.Create;
for i:=1 to ParamCount do
CmdlineParameters.Add(ParamStr(i));
ParseCommandLineParameters(CmdlineParameters);
if Assigned(FCmdlineConnectionParams) then begin
// Minimal parameter for command line mode is hostname
Connected := InitConnection(FCmdlineConnectionParams, FCmdlineSessionName);
end else if GetRegValue(REGNAME_AUTORECONNECT, DEFAULT_AUTORECONNECT) then begin
// Auto connection via preference setting
// Do not autoconnect if we're in commandline mode and the connection was not successful
LastSession := GetRegValue(REGNAME_LASTSESSION, '');
if LastSession <> '' then begin
LoadedParams := LoadConnectionParams(LastSession);
Connected := InitConnection(LoadedParams, LastSession);
end;
end;
// Display session manager
if not Connected then begin
// Cannot be done in OnCreate because we need ready forms here:
if not Assigned(SessionManager) then
SessionManager := TConnForm.Create(Self);
DlgResult := mrCancel;
try
DlgResult := SessionManager.ShowModal;
except
// Work around VCL bug: Suppress access violation in TCustomForm.IsFormSizeStored
// when closing dialog via Alt+F4
end;
if DlgResult = mrCancel then begin
Free;
Exit;
end;
end;
DoAfterConnect;
// Load SQL file(s) by command line
for i:=0 to FCmdlineFilenames.Count-1 do begin
if i>0 then
actNewQueryTabExecute(Self);
if not QueryLoad(FCmdlineFilenames[i]) then
actCloseQueryTabExecute(Self);
end;
end;
procedure TMainForm.ParseCommandLineParameters(Parameters: TStringlist);
var
rx: TRegExpr;
AllParams, Host, User, Pass, Socket: String;
i, Port: Integer;
function GetParamValue(ShortName, LongName: String): String;
begin
Result := '';
rx.Expression := '\s(\-'+ShortName+'|\-\-'+LongName+')\s*\=?\s*([^\-]\S*)';
if rx.Exec(AllParams) then
Result := rx.Match[2];
end;
begin
// Initialize and clear variables
if not Assigned(FCmdlineFilenames) then
FCmdlineFilenames := TStringlist.Create;
FCmdlineFilenames.Clear;
FCmdlineSessionName := '';
FreeAndNil(FCmdlineConnectionParams);
// Prepend a space, so the regular expression can request a mandantory space
// before each param name including the first one
AllParams := ' ' + ImplodeStr(' ', Parameters);
rx := TRegExpr.Create;
FCmdlineSessionName := GetParamValue('d', 'description');
if FCmdlineSessionName <> '' then begin
try
FCmdlineConnectionParams := LoadConnectionParams(FCmdlineSessionName);
except
on E:Exception do begin
// Session params not found in registry
LogSQL(E.Message);
FCmdlineSessionName := '';
end;
end;
end;
// Test if params were passed. If given, override previous values loaded from registry.
// Enables the user to log into a session with a different, non-stored user: -dSession -uSomeOther
Host := GetParamValue('h', 'host');
User := GetParamValue('u', 'user');
Pass := GetParamValue('p', 'password');
Socket := GetParamValue('S', 'socket');
Port := StrToIntDef(GetParamValue('P', 'port'), 0);
// Leave out support for startup script, seems reasonable for command line connecting
if (Host <> '') or (User <> '') or (Pass <> '') or (Port <> 0) or (Socket <> '') then begin
if not Assigned(FCmdlineConnectionParams) then
FCmdlineConnectionParams := TConnectionParameters.Create;
if Host <> '' then FCmdlineConnectionParams.Hostname := Host;
if User <> '' then FCmdlineConnectionParams.Username := User;
if Pass <> '' then FCmdlineConnectionParams.Password := Pass;
if Port <> 0 then FCmdlineConnectionParams.Port := Port;
if Socket <> '' then begin
FCmdlineConnectionParams.Hostname := Socket;
FCmdlineConnectionParams.NetType := ntNamedPipe;
end;
// Ensure we have a session name to pass to InitConnection
if (FCmdlineSessionName = '') and (FCmdlineConnectionParams.Hostname <> '') then
FCmdlineSessionName := FCmdlineConnectionParams.Hostname;
end;
// Check for valid filename(s) in parameters
for i:=0 to Parameters.Count-1 do begin
if FileExists(Parameters[i]) then
FCmdlineFilenames.Add(Parameters[i]);
end;
end;
procedure TMainForm.actSessionManagerExecute(Sender: TObject);
begin
if not Assigned(SessionManager) then
SessionManager := TConnForm.Create(Self);
if SessionManager.ShowModal <> mrCancel then
DoAfterConnect;
end;
procedure TMainForm.DoAfterConnect;
var
i, j: Integer;
lastUsedDB, StartupScript, StartupSQL: String;
functioncats, StartupBatch: TStringList;
miGroup,
miFilterGroup,
miFunction,
miFilterFunction: TMenuItem;
begin
DataGridHasChanges := False;
// Activate logging
if GetRegValue(REGNAME_LOGTOFILE, DEFAULT_LOGTOFILE) then
ActivateFileLogging;
tabHost.Caption := 'Host: '+Connection.Parameters.HostName;
ShowStatusMsg('MySQL '+Connection.ServerVersionStr, 3);
// Save server version
OpenRegistry(SessionName);
Mainreg.WriteInteger(REGNAME_SERVERVERSION, Connection.ServerVersionInt);
Mainreg.WriteString(REGNAME_LASTCONNECT, DateTimeToStr(Now));
// Process startup script
StartupScript := Trim(Connection.Parameters.StartupScriptFilename);
if StartupScript <> '' then begin
if not FileExists(StartupScript) then
MessageDlg('Error: Startup script file not found: '+StartupScript, mtError, [mbOK], 0)
else begin
StartupSQL := ReadTextfile(StartupScript);
StartupBatch := ParseSQL(StartupSQL);
for i:=0 to StartupBatch.Count-1 do try
Connection.Query(StartupBatch[i]);
except
// Suppress popup, errors get logged into SQL log
end;
StartupBatch.Free;
end;
end;
// Remove db and table nodes, force host node to initialize again
InvalidateVT(DBtree, VTREE_NOTLOADED_PURGECACHE, False);
DBTree.Color := GetRegValue(REGNAME_TREEBACKGROUND, clWindow, SessionName);
// Reselect last used database
if GetRegValue( REGNAME_RESTORELASTUSEDDB, DEFAULT_RESTORELASTUSEDDB ) then begin
lastUsedDB := GetRegValue(REGNAME_LASTUSEDDB, '', SessionName);
if lastUsedDB <> '' then try
ActiveDatabase := lastUsedDB;
except
// Suppress exception message when db was dropped externally or
// the session was just opened with "OnlyDBs" in place and the
// last db is not contained in this list.
end;
end;
// By default, select the host node
if not Assigned(DBtree.FocusedNode) then begin
DBtree.Selected[DBtree.GetFirst] := true;
DBtree.FocusedNode := DBtree.GetFirst;
end;
// Create function menu items in popupQuery and popupFilter
menuQueryInsertFunction.Clear;
menuFilterInsertFunction.Clear;
functioncats := GetFunctionCategories;
for i:=0 to functioncats.Count-1 do begin
// Create a menu item which gets subitems later
miGroup := TMenuItem.Create(popupQuery);
miGroup.Caption := functioncats[i];
menuQueryInsertFunction.Add(miGroup);
miFilterGroup := TMenuItem.Create(popupFilter);
miFilterGroup.Caption := miGroup.Caption;
menuFilterInsertFunction.Add(miFilterGroup);
for j:=0 to Length(MySqlFunctions)-1 do begin
if MySqlFunctions[j].Category <> functioncats[i] then
continue;
miFunction := TMenuItem.Create(popupQuery);
miFunction.Caption := MySqlFunctions[j].Name;
miFunction.ImageIndex := 13;
// Prevent generating a hotkey
miFunction.Caption := StringReplace(miFunction.Caption, '&', '&&', [rfReplaceAll]);
// Prevent generating a seperator line
if miFunction.Caption = '-' then
miFunction.Caption := '&-';
miFunction.Hint := MySqlFunctions[j].Name + MySqlFunctions[j].Declaration;
// Take care of needed server version
if MySqlFunctions[j].Version <= Connection.ServerVersionInt then begin
if MySqlFunctions[j].Description <> '' then
miFunction.Hint := miFunction.Hint + ' - ' + Copy(MySqlFunctions[j].Description, 0, 200 );
miFunction.Tag := j;
// Place menuitem on menu
miFunction.OnClick := insertFunction;
end else begin
miFunction.Hint := miFunction.Hint + ' - ('+STR_NOTSUPPORTED+', needs >= '+Connection.ConvertServerVersion(MySqlFunctions[j].Version)+')';
miFunction.Enabled := False;
end;
// Prevent generating a seperator for ShortHint and LongHint
miFunction.Hint := StringReplace( miFunction.Hint, '|', '<27>', [rfReplaceAll] );
miGroup.Add(miFunction);
// Create a copy of the menuitem for popupFilter
miFilterFunction := TMenuItem.Create(popupFilter);
miFilterFunction.Caption := miFunction.Caption;
miFilterFunction.Hint := miFunction.Hint;
miFilterFunction.ImageIndex := miFunction.ImageIndex;
miFilterFunction.Tag := miFunction.Tag;
miFilterFunction.OnClick := miFunction.OnClick;
miFilterFunction.Enabled := miFunction.Enabled;
miFilterGroup.Add(miFilterFunction);
end;
end;
end;
procedure TMainForm.DoDisconnect;
begin
// Do nothing in case user clicked Cancel on session manager
if (not Assigned(Connection)) or (not Connection.Active) then
Exit;
// Open server-specific registry-folder.
// relative from already opened folder!
OpenRegistry(SessionName);
MainReg.WriteString( REGNAME_LASTUSEDDB, Connection.Database );
// Post pending UPDATE
if DataGridHasChanges then
actDataPostChangesExecute(Self);
// Clear database and table lists
DBtree.ClearSelection;
DBtree.FocusedNode := nil;
PreviousFocusedNode := nil;
FreeAndNil(AllDatabasesDetails);
FreeAndNil(DataGridHiddenColumns);
SynMemoFilter.Clear;
SetLength(DataGridSortColumns, 0);
// Closing connection
Connection.Active := False;
if prefLogToFile then
DeactivateFileLogging;
// Invalidate list contents
InvalidateVT(ListDatabases, VTREE_NOTLOADED, False);
InvalidateVT(ListVariables, VTREE_NOTLOADED, False);
InvalidateVT(ListStatus, VTREE_NOTLOADED, False);
InvalidateVT(ListProcesses, VTREE_NOTLOADED, False);
InvalidateVT(ListCommandstats, VTREE_NOTLOADED, False);
InvalidateVT(ListTables, VTREE_NOTLOADED, False);
Application.Title := APPNAME;
end;
procedure TMainForm.actCreateDatabaseExecute(Sender: TObject);
begin
// Create database:
// Create modal form once on demand
if CreateDatabaseForm = nil then
CreateDatabaseForm := TCreateDatabaseForm.Create(Self);
// Rely on the modalresult being set correctly
if CreateDatabaseForm.ShowModal = mrOK then
InvalidateVT(DBtree, VTREE_NOTLOADED_PURGECACHE, False);
end;
procedure TMainForm.actImportCSVExecute(Sender: TObject);
begin
// Import Textfile
if not Assigned(ImportTextfileDialog) then
ImportTextfileDialog := Tloaddataform.Create(Self);
ImportTextfileDialog.ShowModal;
end;
procedure TMainForm.actPreferencesExecute(Sender: TObject);
begin
// Preferences
if OptionsForm = nil then
OptionsForm := Toptionsform.Create(Self);
OptionsForm.ShowModal;
end;
procedure TMainForm.actReadmeExecute(Sender: TObject);
begin
// show readme.txt
ShellExec( 'readme.txt', ExtractFilePath(paramstr(0)) );
end;
procedure TMainForm.FormResize(Sender: TObject);
var
i, room: Integer;
PanelRect: TRect;
begin
// Exit early when user pressed "Cancel" on connection dialog
if csDestroying in ComponentState then
Exit;
room := 0;
for i := 1 to Statusbar.Panels.Count - 1 do
inc(room, Statusbar.Panels[i].Width);
StatusBar.Panels[0].Width := Statusbar.Width - room;
// Retreive the rectancle of the statuspanel (in our case the fifth panel)
SendMessage(StatusBar.Handle, SB_GETRECT, 5, Integer(@PanelRect));
// Position the progressbar over the panel on the statusbar
with PanelRect do
ProgressBarStatus.SetBounds(Left, Top, Right-Left, Bottom-Top);
lblDataTop.Width := pnlDataTop.Width - tlbDataButtons.Width - 10;
FixQueryTabCloseButtons;
end;
procedure TMainForm.actUserManagerExecute(Sender: TObject);
begin
if UserManagerForm = nil then
UserManagerForm := TUserManagerForm.Create(Self);
if UserManagerForm.TestUserAdmin then
UserManagerForm.ShowModal;
end;
procedure TMainForm.actAboutBoxExecute(Sender: TObject);
var
Box: TAboutBox;
begin
// Info-Box
Box := TAboutBox.Create(Self);
Box.ShowModal;
Box.Free;
end;
procedure TMainForm.actClearEditorExecute(Sender: TObject);
var
m: TSynMemo;
begin
if Sender = actClearQueryEditor then
m := ActiveQueryMemo
else begin
m := SynMemoFilter;
editFilterSearch.Clear;
end;
m.SelectAll;
m.SelText := '';
m.SelStart := 0;
m.SelEnd := 0;
if QueryTabActive then begin
SetTabCaption(PageControlMain.ActivePageIndex, '');
ActiveQueryTab.MemoFilename := '';
ActiveQueryTab.Memo.Modified := False;
end;
if m = SynMemoFilter then
InvalidateVT(DataGrid, VTREE_NOTLOADED_PURGECACHE, False);
end;
procedure TMainForm.actTableToolsExecute(Sender: TObject);
var
Act: TAction;
InDBTree: Boolean;
Node: PVirtualNode;
begin
// Show table tools dialog
if TableToolsDialog = nil then
TableToolsDialog := TfrmTableTools.Create(Self);
Act := Sender as TAction;
InDBTree := (Act.ActionComponent is TMenuItem)
and (TPopupMenu((Act.ActionComponent as TMenuItem).GetParentMenu).PopupComponent = DBTree);
if InDBTree then
TableToolsDialog.SelectedTables.Text := SelectedTable.Name
else begin
TableToolsDialog.SelectedTables.Clear;
Node := ListTables.GetFirstSelected;
while Assigned(Node) do begin
TableToolsDialog.SelectedTables.Add(ListTables.Text[Node, 0]);
Node := ListTables.GetNextSelected(Node);
end;
end;
if Sender = actMaintenance then
TableToolsDialog.ToolMode := tmMaintenance
else if Sender = actFindTextOnServer then
TableToolsDialog.ToolMode := tmFind
else if Sender = actExportTables then
TableToolsDialog.ToolMode := tmSQLExport
else if Sender = actBulkTableEdit then
TableToolsDialog.ToolMode := tmBulkTableEdit;
TableToolsDialog.ShowModal;
end;
{**
Edit view
}
procedure TMainForm.actPrintListExecute(Sender: TObject);
var
f: TForm;
begin
// Print contents of a list or grid
f := TPrintlistForm.Create(Self);
f.ShowModal;
FreeAndNil(f);
end;
procedure TMainForm.actCopyTableExecute(Sender: TObject);
begin
// copy table
if not Assigned(CopyTableDialog) then
CopyTableDialog := TCopyTableForm.Create(Self);
CopyTableDialog.ShowModal;
end;
procedure TMainForm.menuConnectionsPopup(Sender: TObject);
var
i: integer;
item: TMenuItem;
Connections: TStringList;
begin
// Delete dynamically added connection menu items.
for i := menuConnections.Items.Count - 1 downto 0 do begin
menuConnections.Items.Delete(i);
end;
// "Session manager" and "New window" items
item := TMenuItem.Create(menuConnections);
item.Action := actSessionManager;
item.Default := True;
menuConnections.Items.Add(item);
item := TMenuItem.Create(menuConnections);
item.Action := actNewWindow;
menuConnections.Items.Add(item);
// All sessions
if MainReg.OpenKey(REGPATH + REGKEY_SESSIONS, False) then begin
Connections := TStringList.Create;
MainReg.GetKeyNames(Connections);
for i := 0 to Connections.Count - 1 do begin
item := TMenuItem.Create(menuConnections);
item.Caption := Connections[i];
item.OnClick := SessionConnect;
item.ImageIndex := 37;
if Connections[i] = SessionName then begin
item.Checked := True;
item.ImageIndex := -1;
end;
menuConnections.Items.Add(item);
end;
end;
end;
procedure TMainForm.File1Click(Sender: TObject);
var
Item: TMenuItem;
i: Integer;
Connections: TStringList;
begin
// Decide if "Connect to" menu should be enabled
menuConnectTo.Enabled := False;
if MainReg.OpenKey(REGPATH + REGKEY_SESSIONS, False) then begin
menuConnectTo.Enabled := MainReg.HasSubKeys;
if menuConnectTo.Enabled then begin
// Add all sessions to submenu
for i := menuConnectTo.Count - 1 downto 0 do
menuConnectTo.Delete(i);
Connections := TStringList.Create;
MainReg.GetKeyNames(Connections);
for i := 0 to Connections.Count - 1 do begin
Item := TMenuItem.Create(menuConnectTo);
Item.Caption := Connections[i];
Item.OnClick := SessionConnect;
Item.ImageIndex := 37;
if Connections[i] = SessionName then begin
Item.Checked := True;
Item.ImageIndex := -1;
end;
menuConnectTo.Add(Item);
end;
end;
end;
end;
procedure TMainForm.actWebbrowse(Sender: TObject);
begin
// Browse to URL (hint)
ShellExec( TAction(Sender).Hint );
end;
// Escape database, table, field, index or key name.
function TMainform.mask(str: String) : String;
begin
result := Connection.QuoteIdent(str);
end;
// Quote identifier, probably with multiple segments, e.g. db.table.column
function TMainform.MaskMulti(str: String): String;
var
Segments: TStringList;
i: Integer;
begin
Segments := Explode('.', str);
Result := '';
for i:=0 to Segments.Count-1 do
Result := Result + mask(Segments[i]) + '.';
FreeAndNil(Segments);
Delete(Result, Length(Result), 1);
end;
procedure TMainForm.actExportSettingsExecute(Sender: TObject);
begin
// Export settings to .reg-file
if SaveDialog2.Execute then
ShellExec('regedit.exe', '', '/e "'+SaveDialog2.FileName+'" HKEY_CURRENT_USER'+REGPATH);
end;
procedure TMainForm.actImportSettingsExecute(Sender: TObject);
begin
// Import settings from .reg-file
if OpenDialog2.Execute then
ShellExec('regedit.exe', '', '"'+OpenDialog2.FileName+'"');
end;
procedure TMainForm.actExecuteQueryExecute(Sender: TObject);
begin
ExecSqlClick(sender, false);
end;
procedure TMainForm.actExecuteSelectionExecute(Sender: TObject);
begin
ExecSqlClick(sender, true);
end;
procedure TMainForm.actExecuteLineExecute(Sender: TObject);
begin
ExecSqlClick(sender, false, true);
end;
procedure TMainForm.actCopyAsCSVExecute(Sender: TObject);
var
S: TMemoryStream;
begin
// Copy data in focused grid as CSV
Screen.Cursor := crHourglass;
S := TMemoryStream.Create;
try
GridToCsv(ActiveGrid, prefCSVSeparator, prefCSVEncloser, prefCSVTerminator, S);
StreamToClipboard(S, nil, False);
finally
ShowStatusMsg('Freeing data...');
S.Free;
ShowStatusMsg(STATUS_MSG_READY);
Screen.Cursor := crDefault;
end;
end;
procedure TMainForm.actCopyAsHTMLExecute(Sender: TObject);
var
S: TMemoryStream;
Title: String;
begin
// Copy data in focused grid as HTML table
Screen.Cursor := crHourglass;
S := TMemoryStream.Create;
if ActiveGrid = DataGrid then Title := SelectedTable.Name
else Title := 'SQL query';
try
GridToHtml(ActiveGrid, Title, S);
StreamToClipboard(S, S, True);
finally
ShowStatusMsg('Freeing data...');
S.Free;
ShowStatusMsg(STATUS_MSG_READY);
Screen.Cursor := crDefault;
end;
end;
procedure TMainForm.actCopyAsXMLExecute(Sender: TObject);
var
S: TMemoryStream;
Root: String;
begin
// Copy data in focused grid as XML
Screen.Cursor := crHourglass;
S := TMemoryStream.Create;
if ActiveGrid = DataGrid then Root := SelectedTable.Name
else Root := 'SQL query';
try
GridToXml(ActiveGrid, Root, S);
StreamToClipboard(S, nil, False);
finally
ShowStatusMsg('Freeing data...');
S.Free;
ShowStatusMsg(STATUS_MSG_READY);
Screen.Cursor := crDefault;
end;
end;
procedure TMainForm.actCopyAsSQLExecute(Sender: TObject);
var
S, HTML: TMemoryStream;
Tablename: String;
Content: AnsiString;
begin
// Copy data in focused grid as SQL
Screen.Cursor := crHourglass;
S := TMemoryStream.Create;
if ActiveGrid = DataGrid then Tablename := SelectedTable.Name
else Tablename := 'unknown';
try
GridToSql(ActiveGrid, Tablename, S);
SetLength(Content, S.Size);
S.Position := 0;
S.Read(PAnsiChar(Content)^, S.Size);
SynExporterHTML1.ExportAll(Explode(CRLF, UTF8ToString(Content)));
HTML := TMemoryStream.Create;
SynExporterHTML1.SaveToStream(HTML);
StreamToClipboard(S, HTML, False);
finally
ShowStatusMsg('Freeing data...');
S.Free;
ShowStatusMsg(STATUS_MSG_READY);
Screen.Cursor := crDefault;
end;
end;
procedure TMainForm.actExportDataExecute(Sender: TObject);
var
Dialog: TSaveDialog;
FS: TFileStream;
Title: String;
begin
// Save data in current dataset as CSV, HTML or XML
Dialog := SaveDialogExportData;
if ActiveGrid = DataGrid then
Title := SelectedTable.Name
else
Title := 'SQL query';
Dialog.FileName := Title;
Dialog.Title := 'Export result set from '+Dialog.Filename+'...';
if Dialog.Execute and (Dialog.FileName <> '') then try
Screen.Cursor := crHourGlass;
FS := TFileStream.Create(Dialog.FileName, fmCreate or fmOpenWrite);
case Dialog.FilterIndex of
1: GridToCsv(ActiveGrid, prefCSVSeparator, prefCSVEncloser, prefCSVTerminator, FS);
2: GridToHtml(ActiveGrid, Title, FS);
3: GridToXml(ActiveGrid, Title, FS);
4: GridToSql(ActiveGrid, Title, FS);
end;
ShowStatusMsg('Freeing data...');
FS.Free;
finally
ShowStatusMsg(STATUS_MSG_READY);
Screen.Cursor := crDefault;
end;
end;
// view HTML
procedure TMainForm.actHTMLviewExecute(Sender: TObject);
const
msgNotBinary = 'Non-binary field selected. Only binary fields containing JPEG, PNG, GIF and BMP images are supported.';
msgNotImage = 'Unrecognized image format. Only JPEG, PNG, GIF and BMP are supported.';
var
g : TVirtualStringTree;
filename : String;
f : Textfile;
Header, Content : AnsiString;
IsBinary : Boolean;
SaveBinary : Boolean;
begin
g := ActiveGrid;
if g = nil then begin messagebeep(MB_ICONASTERISK); exit; end;
Screen.Cursor := crHourGlass;
ShowStatusMsg('Saving contents to file...');
IsBinary := GridResult(ActiveGrid).Columns[g.FocusedColumn].DatatypeCat = dtcBinary;
DataGridEnsureFullRow(g, g.FocusedNode);
Header := WideHexToBin(Copy(g.Text[g.FocusedNode, g.FocusedColumn], 3, 20));
SaveBinary := false;
filename := GetTempDir+'\'+APPNAME+'-preview.';
if IsBinary and (Copy(Header, 7, 4) = 'JFIF') then begin
SaveBinary := true;
filename := filename + 'jpeg';
end else if IsBinary and (Copy(Header, 2, 3) = 'PNG') then begin
SaveBinary := true;
filename := filename + 'png';
end else if IsBinary and (Copy(Header, 1, 3) = 'GIF') then begin
SaveBinary := true;
filename := filename + 'gif';
end else if IsBinary and (Copy(Header, 1, 2) = 'BM') then begin
SaveBinary := true;
filename := filename + 'bmp';
end else if IsBinary and (Copy(Header, 3, 2) = #42#0) then begin
SaveBinary := true;
filename := filename + 'tif';
end;
if not IsBinary then begin
MessageDlg(msgNotBinary, mtWarning, [mbOk], 0);
end else if not SaveBinary then begin
MessageDlg(msgNotImage, mtWarning, [mbOk], 0);
end;
if SaveBinary then begin
Content := WideHexToBin(Copy(g.Text[g.FocusedNode, g.FocusedColumn], 3, High(Integer)));
AssignFile(f, filename);
Rewrite(f);
Write(f, Content);
CloseFile(f);
end;
ShowStatusMsg( STATUS_MSG_READY );
Screen.Cursor := crDefault;
ShellExec( filename );
end;
procedure TMainForm.actInsertFilesExecute(Sender: TObject);
begin
if not Assigned(InsertFiles) then
InsertFiles := TfrmInsertFiles.Create(Self);
InsertFiles.ShowModal;
end;
// Drop Table(s)
procedure TMainForm.actDropObjectsExecute(Sender: TObject);
var
msg, activeDB : String;
InDBTree: Boolean;
Act: TAction;
Node: PVirtualNode;
Obj: PDBObject;
DBObject: TDBObject;
ObjectList: TDBObjectList;
begin
debug('drop objects activated');
// Set default database name to to ActiveDatabase.
// Can be overwritten when someone selects a table in dbtree from different database
activeDB := ActiveDatabase;
ObjectList := TDBobjectList.Create(False);
Act := Sender as TAction;
InDBTree := (Act.ActionComponent is TMenuItem)
and (TPopupMenu((Act.ActionComponent as TMenuItem).GetParentMenu).PopupComponent = DBTree);
if InDBTree then begin
// drop table selected in tree view.
case GetFocusedTreeNodeType of
lntDb: begin
if MessageDlg('Drop Database "'+activeDB+'"?' + crlf + crlf + 'WARNING: You will lose all objects in database '+activeDB+'!', mtConfirmation, [mbok,mbcancel], 0) <> mrok then
Abort;
try
Connection.Query('DROP DATABASE ' + mask(activeDB));
Connection.ClearDbObjects(activeDB);
InvalidateVT(DBtree, VTREE_NOTLOADED_PURGECACHE, False);
ActiveDatabase := '';
except
on E:Exception do
MessageDlg(E.Message, mtError, [mbOK], 0);
end;
Exit;
end;
else ObjectList.Add(SelectedTable);
end;
end else begin
// Invoked from database tab
Node := ListTables.GetFirstSelected;
while Assigned(Node) do begin
Obj := ListTables.GetNodeData(Node);
ObjectList.Add(Obj^);
Node := ListTables.GetNextSelected(Node);
end;
end;
// Fix actions temporarily enabled for popup menu.
ValidateControls(Sender);
// Safety stop to avoid firing DROP TABLE without tablenames
if ObjectList.Count = 0 then
Exit;
// Ask user for confirmation to drop selected objects
msg := 'Drop ' + IntToStr(ObjectList.Count) + ' object(s) in database "'+activeDB+'"?' + CRLF + CRLF;
for DBObject in ObjectList do
msg := msg + DBObject.Name + ', ';
Delete(msg, Length(msg)-1, 2);
if MessageDlg(msg, mtConfirmation, [mbok,mbcancel], 0) <> mrok then
Exit;
try
// Compose and run DROP [TABLE|VIEW|...] queries
for DBObject in ObjectList do
Connection.Query('DROP '+UpperCase(DBObject.ObjType)+' '+Mask(DBObject.Name));
// Refresh ListTables + dbtree so the dropped tables are gone:
Connection.ClearDbObjects(ActiveDatabase);
except
on E:Exception do
MessageDlg(E.Message, mtError, [mbOK], 0);
end;
ObjectList.Free;
end;
// Load SQL-file, make sure that SheetQuery is activated
procedure TMainForm.actLoadSQLExecute(Sender: TObject);
var
i: Integer;
begin
if OpenDialogSQLfile.Execute then begin
for i:=0 to OpenDialogSQLfile.Files.Count-1 do begin
if i > 0 then
actNewQueryTabExecute(Sender);
QueryLoad(OpenDialogSQLfile.Files[i]);
end;
end;
end;
procedure TMainForm.SessionConnect(Sender: TObject);
var
Session: String;
Params: TConnectionParameters;
begin
Session := (Sender as TMenuItem).Caption;
Params := LoadConnectionParams(Session);
if InitConnection(Params, Session) then
DoAfterConnect;
end;
{**
Receive connection parameters and create the mdi-window
Paremeters are either sent by connection-form or by commandline.
}
function TMainform.InitConnection(Params: TConnectionParameters; Session: String): Boolean;
var
ConnectionAttempt: TMySQLConnection;
SessionExists: Boolean;
begin
ConnectionAttempt := TMySQLConnection.Create(Self);
ConnectionAttempt.OnLog := LogSQL;
ConnectionAttempt.OnDatabaseChanged := DatabaseChanged;
ConnectionAttempt.OnDBObjectsCleared := DBObjectsCleared;
ConnectionAttempt.ObjectNamesInSelectedDB := SynSQLSyn1.TableNames;
ConnectionAttempt.Parameters := Params;
try
ConnectionAttempt.Active := True;
except
on E:Exception do
MessageDlg(E.Message, mtError, [mbOK], 0);
end;
// attempt to establish connection
SessionExists := MainReg.KeyExists(REGPATH + REGKEY_SESSIONS + Session);
if not ConnectionAttempt.Active then begin
// attempt failed
if SessionExists then begin
// Save "refused" counter
OpenRegistry(Session);
MainReg.WriteInteger(REGNAME_REFUSEDCOUNT, GetRegValue(REGNAME_REFUSEDCOUNT, 0, Session)+1);
end;
Result := False;
FreeAndNil(ConnectionAttempt);
end else begin
if SessionExists then begin
// Save "refused" counter
OpenRegistry(Session);
MainReg.WriteInteger(REGNAME_CONNECTCOUNT, GetRegValue(REGNAME_CONNECTCOUNT, 0, Session)+1);
// Save last session name in root folder
OpenRegistry;
MainReg.WriteString(REGNAME_LASTSESSION, Session);
end;
Result := True;
DoDisconnect;
FreeAndNil(Connection);
Connection := ConnectionAttempt;
SessionName := Session;
end;
ShowStatusMsg( STATUS_MSG_READY );
end;
procedure TMainForm.actDataDeleteExecute(Sender: TObject);
begin
// Delete row(s)
if (DataGrid.SelectedCount = 1) and
(DataGridResult.Rows[DataGrid.GetFirstSelected.Index].State = grsInserted)
then begin
// Deleting the virtual row which is only in memory by stopping edit mode
actDataCancelChanges.Execute;
end else begin
// The "normal" case: Delete existing rows
if DataGrid.SelectedCount = 0 then
MessageDLG('Please select one or more rows to delete them.', mtError, [mbOK], 0)
else if MessageDLG('Delete '+inttostr(DataGrid.SelectedCount)+' row(s)?',
mtConfirmation, [mbOK, mbCancel], 0) = mrOK then begin
GridPostDelete(DataGrid);
end;
end;
end;
procedure TMainForm.actUpdateCheckExecute(Sender: TObject);
var
frm : TfrmUpdateCheck;
begin
frm := TfrmUpdateCheck.Create(Self);
frm.ShowModal;
FreeAndNil(frm);
end;
procedure TMainForm.actCreateDBObjectExecute(Sender: TObject);
var
Obj: TDBObject;
a: TAction;
begin
// Create a new table, view, etc.
tabEditor.TabVisible := True;
PagecontrolMain.ActivePage := tabEditor;
a := Sender as TAction;
Obj := TDBObject.Create;
if a = actCreateTable then Obj.NodeType := lntTable
else if a = actCreateView then Obj.NodeType := lntView
else if a = actCreateRoutine then Obj.NodeType := lntProcedure
else if a = actCreateTrigger then Obj.NodeType := lntTrigger
else if a = actCreateEvent then Obj.NodeType := lntEvent;
PlaceObjectEditor(Obj);
end;
procedure TMainForm.actEmptyTablesExecute(Sender: TObject);
var
t: TStringList;
i: Integer;
sql_pattern: String;
begin
// Add selected items/tables to helper list
if ListTables.Focused then
t := GetVTCaptions(ListTables, True)
else if DBTree.Focused then begin
t := TStringList.Create;
t.Add(SelectedTable.Name);
end else
Exit;
if t.Count = 0 then
Exit;
if MessageDlg('Empty ' + IntToStr(t.count) + ' table(s) ?' + CRLF + '(' + implodestr(', ', t) + ')',
mtConfirmation, [mbOk, mbCancel], 0) <> mrOk then
exit;
Screen.Cursor := crHourglass;
{**
@note ansgarbecker: Empty table using faster TRUNCATE statement on newer servers
@see http://dev.mysql.com/doc/refman/5.0/en/truncate.html
@see https://sourceforge.net/tracker/index.php?func=detail&aid=1644143&group_id=164593&atid=832350
}
if Connection.ServerVersionInt < 50003 then
sql_pattern := 'DELETE FROM '
else
sql_pattern := 'TRUNCATE ';
try
for i:=0 to t.count-1 do
Connection.Query( sql_pattern + mask(t[i]) );
actRefresh.Execute;
except
on E:Exception do
MessageDlg(E.Message, mtError, [mbOK], 0);
end;
t.Free;
Screen.Cursor := crDefault;
end;
procedure TMainForm.actRunRoutinesExecute(Sender: TObject);
var
Tab: TQueryTab;
Query, ParamInput, ProcOrFunc,
Returns, DataAccess, Security, Comment, Body: String;
Deterministic: Boolean;
i: Integer;
pObj: PDBObject;
Obj: TDBObject;
Objects: TDBObjectList;
Node: PVirtualNode;
Parameters: TRoutineParamList;
begin
// Run stored function(s) or procedure(s)
Objects := TDBObjectList.Create(False);
if ListTables.Focused then begin
Node := ListTables.GetFirstSelected;
while Assigned(Node) do begin
pObj := ListTables.GetNodeData(Node);
if pObj.NodeType in [lntProcedure, lntFunction] then
Objects.Add(pObj^);
Node := ListTables.GetNextSelected(Node);
end;
end else
Objects.Add(SelectedTable);
if Objects.Count = 0 then
MessageDlg('Please select one or more stored function(s) or routine(s).', mtError, [mbOK], 0);
for Obj in Objects do begin
actNewQueryTab.Execute;
Tab := QueryTabs[MainForm.QueryTabs.Count-1];
case Obj.NodeType of
lntProcedure: begin
Query := 'CALL ';
ProcOrFunc := 'PROCEDURE';
end;
lntFunction: begin
Query := 'SELECT ';
ProcOrFunc := 'FUNCTION';
end;
end;
Parameters := TRoutineParamList.Create;
ParseRoutineStructure(Connection.GetVar('SHOW CREATE '+ProcOrFunc+' '+mask(Obj.Name), 2),
Parameters,
Deterministic, Returns, DataAccess, Security, Comment, Body
);
Query := Query + mask(Obj.Name);
ParamInput := '';
for i:=0 to Parameters.Count-1 do begin
if ParamInput <> '' then
ParamInput := ParamInput + ', ';
ParamInput := ParamInput + '''' + InputBox(Obj.Name, 'Parameter #'+IntToStr(i+1)+': '+Parameters[i].Name+' ('+Parameters[i].Datatype+')', '') + '''';
end;
Parameters.Free;
Query := Query + '('+ParamInput+')';
Tab.Memo.Text := Query;
actExecuteQueryExecute(Sender);
end;
end;
procedure TMainForm.actNewWindowExecute(Sender: TObject);
begin
debug('perf: new connection clicked.');
ShellExec( ExtractFileName(paramstr(0)), ExtractFilePath(paramstr(0)) );
end;
procedure TMainForm.actQueryFindReplaceExecute(Sender: TObject);
var
DlgResult: TModalResult;
begin
// Display search + replace dialog
if not Assigned(SearchReplaceDialog) then
SearchReplaceDialog := TfrmSearchReplace.Create(Self);
SearchReplaceDialog.Editor := ActiveQueryMemo;
SearchReplaceDialog.chkReplace.Checked := Sender = actQueryReplace;
DlgResult := SearchReplaceDialog.ShowModal;
case DlgResult of
mrOK, mrAll: begin
DoSearchReplace;
FSearchReplaceExecuted := True; // Helper for later F3 hits
end;
mrCancel: Exit;
end;
end;
procedure TMainForm.actQueryFindAgainExecute(Sender: TObject);
begin
// F3 - search or replace again, using previous settings
if not FSearchReplaceExecuted then
actQueryFindReplaceExecute(Sender)
else begin
SearchReplaceDialog.Editor := ActiveQueryMemo;
DoSearchReplace;
end;
end;
procedure TMainForm.DoSearchReplace;
var
Occurences: Integer;
OldCaretXY: TBufferCoord;
begin
if SearchReplaceDialog.chkRegularExpression.Checked then
SearchReplaceDialog.Editor.SearchEngine := SynEditRegexSearch1
else
SearchReplaceDialog.Editor.SearchEngine := SynEditSearch1;
OldCaretXY := SearchReplaceDialog.Editor.CaretXY;
SearchReplaceDialog.Editor.BeginUpdate;
ShowStatusMsg('Searching ...');
Occurences := SearchReplaceDialog.Editor.SearchReplace(
SearchReplaceDialog.comboSearch.Text,
SearchReplaceDialog.comboReplace.Text,
SearchReplaceDialog.Options
);
SearchReplaceDialog.Editor.EndUpdate;
ShowStatusMsg(STATUS_MSG_READY);
if ssoReplaceAll in SearchReplaceDialog.Options then
ShowStatusMsg('Text "'+SearchReplaceDialog.comboSearch.Text+'" '+FormatNumber(Occurences)+' times replaced.', 0)
else begin
if (OldCaretXY.Char = SearchReplaceDialog.Editor.CaretXY.Char) and
(OldCaretXY.Line = SearchReplaceDialog.Editor.CaretXY.Line) then
MessageDlg('Text "'+SearchReplaceDialog.comboSearch.Text+'" not found.', mtInformation, [mbOk], 0);
end;
end;
procedure TMainForm.SynMemoQueryReplaceText(Sender: TObject; const ASearch,
AReplace: string; Line, Column: Integer; var Action: TSynReplaceAction);
begin
// Fires when "Replace all" in search dialog was pressed with activated "Prompt on replace"
case MessageDlg('Replace this occurrence of "'+sstr(ASearch, 100)+'"?', mtConfirmation, [mbYes, mbYesToAll, mbNo, mbCancel], 0) of
mrYes: Action := raReplace;
mrYesToAll: Action := raReplaceAll;
mrNo: Action := raSkip;
mrCancel: Action := raCancel;
end;
end;
procedure TMainForm.actRefreshExecute(Sender: TObject);
var
tab1, tab2: TTabSheet;
List: TVirtualStringTree;
begin
// Refresh
// Force data tab update when appropriate.
tab1 := PageControlMain.ActivePage;
if ActiveControl = DBtree then
RefreshTree(True)
else if tab1 = tabHost then begin
tab2 := PageControlHost.ActivePage;
if tab2 = tabDatabases then
List := ListDatabases
else if tab2 = tabVariables then
List := ListVariables
else if tab2 = tabStatus then
List := ListStatus
else if tab2 = tabProcessList then
List := ListProcesses
else
List := ListCommandStats;
InvalidateVT(List, VTREE_NOTLOADED_PURGECACHE, True);
end else if tab1 = tabDatabase then
InvalidateVT(ListTables, VTREE_NOTLOADED_PURGECACHE, False)
else if tab1 = tabData then
InvalidateVT(DataGrid, VTREE_NOTLOADED_PURGECACHE, False);
end;
procedure TMainForm.actSQLhelpExecute(Sender: TObject);
var
keyword : String;
Col: TTableColumn;
begin
// Call SQL Help from various places
if Connection.ServerVersionInt < 40100 then
exit;
keyword := '';
// Query-Tab
if ActiveControl is TSynMemo then
keyword := TSynMemo(ActiveControl).WordAtCursor
// Data-Tab
else if (PageControlMain.ActivePage = tabData)
and Assigned(DataGrid.FocusedNode) then begin
Col := TTableColumn(SelectedTableColumns[DataGrid.FocusedColumn]);
keyword := Col.DataType.Name;
end
else if QueryTabActive and ActiveQueryHelpers.Focused then
begin
// Makes only sense if one of the tabs "SQL fn" or "SQL kw" was selected
if ActiveQueryTabset.TabIndex in [1,2] then
begin
keyword := ActiveQueryHelpers.Items[ActiveQueryHelpers.ItemIndex];
end;
end;
// Clean existing paranthesis, fx: char(64)
if Pos( '(', keyword ) > 0 then
begin
keyword := Copy( keyword, 1, Pos( '(', keyword )-1 );
end;
// Show the window
CallSQLHelpWithKeyword( keyword );
end;
{***
Show SQL Help window directly using a keyword
@param String SQL-keyword
@see FieldeditForm.btnDatatypeHelp
}
procedure TMainform.CallSQLHelpWithKeyword( keyword: String );
begin
if SQLHelpForm = nil then
SQLHelpForm := TfrmSQLhelp.Create(Self);
SQLHelpForm.Show;
SQLHelpForm.Keyword := keyword;
end;
procedure TMainForm.actSaveSQLAsExecute(Sender: TObject);
var
i: Integer;
CanSave: TModalResult;
begin
// Save SQL
CanSave := mrNo;
while (CanSave = mrNo) and SaveDialogSQLFile.Execute do begin
// Save complete content or just the selected text,
// depending on the tag of calling control
CanSave := mrYes;
for i:=0 to QueryTabs.Count-1 do begin
if QueryTabs[i].MemoFilename = SaveDialogSQLFile.FileName then begin
CanSave := MessageDlg('File '+CRLF+'"'+SaveDialogSQLFile.FileName+'"'+CRLF+'is already open in query tab #'+IntToStr(QueryTabs[i].Number)+'. Overwrite it?',
mtWarning, [mbYes, mbNo, mbCancel], 0);
break;
end;
end;
end;
if CanSave = mrYes then begin
SaveQueryMemo(ActiveQueryTab, SaveDialogSQLFile.FileName, (Sender as TAction).Tag = 1);
for i:=0 to QueryTabs.Count-1 do begin
if QueryTabs[i] = ActiveQueryTab then
continue;
if QueryTabs[i].MemoFilename = SaveDialogSQLFile.FileName then
QueryTabs[i].Memo.Modified := True;
end;
ValidateQueryControls(Sender);
end;
end;
procedure TMainForm.actSaveSQLExecute(Sender: TObject);
var
i: Integer;
begin
if ActiveQueryTab.MemoFilename <> '' then begin
SaveQueryMemo(ActiveQueryTab, ActiveQueryTab.MemoFilename, False);
for i:=0 to QueryTabs.Count-1 do begin
if QueryTabs[i] = ActiveQueryTab then
continue;
if QueryTabs[i].MemoFilename = ActiveQueryTab.MemoFilename then
QueryTabs[i].Memo.Modified := True;
end;
ValidateQueryControls(Sender);
end else
actSaveSQLAsExecute(Sender);
end;
procedure TMainForm.SaveQueryMemo(Tab: TQueryTab; Filename: String; OnlySelection: Boolean);
var
Text, LB: String;
begin
Screen.Cursor := crHourGlass;
if OnlySelection then
Text := Tab.Memo.SelText
else
Text := Tab.Memo.Text;
LB := '';
case Tab.MemoLineBreaks of
lbsUnix: LB := LB_UNIX;
lbsMac: LB := LB_MAC;
lbsWide: LB := LB_WIDE;
end;
if LB <> '' then
Text := StringReplace(Text, CRLF, LB, [rfReplaceAll]);
SaveUnicodeFile( Filename, Text );
SetTabCaption(Tab.Number+tabData.PageIndex, ExtractFilename(Filename));
Tab.MemoFilename := Filename;
Tab.Memo.Modified := False;
Screen.Cursor := crDefault;
end;
procedure TMainForm.actSaveSQLSnippetExecute(Sender: TObject);
var
snippetname : String;
mayChange : Boolean;
Text, LB: String;
begin
// Save snippet
if InputQuery( 'Save snippet', 'Snippet name:', snippetname) then
begin
if Copy( snippetname, Length(snippetname)-4, 4 ) <> '.sql' then
snippetname := snippetname + '.sql';
// cleanup snippetname from special characters
snippetname := DirnameSnippets + goodfilename(snippetname);
if FileExists( snippetname ) then
begin
if MessageDlg( 'Overwrite existing snippet '+snippetname+'?', mtConfirmation, [mbOK, mbCancel], 0 ) <> mrOK then
exit;
end;
Screen.Cursor := crHourglass;
// Save complete content or just the selected text,
// depending on the tag of calling control
case (Sender as TComponent).Tag of
0: Text := ActiveQueryMemo.Text;
1: Text := ActiveQueryMemo.SelText;
end;
LB := '';
case ActiveQueryTab.MemoLineBreaks of
lbsUnix: LB := LB_UNIX;
lbsMac: LB := LB_MAC;
lbsWide: LB := LB_WIDE;
end;
if LB <> '' then
Text := StringReplace(Text, CRLF, LB, [rfReplaceAll]);
if not DirectoryExists(DirnameSnippets) then
ForceDirectories(DirnameSnippets);
SaveUnicodeFile( snippetname, Text );
FillPopupQueryLoad;
if ActiveQueryTabset.TabIndex = 3 then begin
// SQL Snippets selected in query helper, refresh list
mayChange := True; // Unused; satisfies callee parameter collection which is probably dictated by tabset.
tabsetQueryHelpersChange(Sender, 3, mayChange);
end;
Screen.Cursor := crDefault;
end;
end;
procedure TMainForm.actQueryStopOnErrorsExecute(Sender: TObject);
begin
// Weird fix: dummy routine to avoid the sending action getting disabled
end;
procedure TMainForm.actQueryWordWrapExecute(Sender: TObject);
begin
ActiveQueryMemo.WordWrap := TAction(Sender).Checked;
end;
procedure TMainform.FillPopupQueryLoad;
var
i, j: Integer;
menuitem, snippetsfolder: TMenuItem;
snippets: TStringList;
sqlFilename: String;
begin
// Fill the popupQueryLoad menu
popupQueryLoad.Items.Clear;
// Snippets
snippets := getFilesFromDir( DirnameSnippets, '*.sql', true );
snippetsfolder := TMenuItem.Create( popupQueryLoad );
snippetsfolder.Caption := 'Snippets';
popupQueryLoad.Items.Add(snippetsfolder);
for i := 0 to snippets.Count - 1 do begin
menuitem := TMenuItem.Create( snippetsfolder );
menuitem.Caption := snippets[i];
menuitem.OnClick := popupQueryLoadClick;
snippetsfolder.Add(menuitem);
end;
// Separator
menuitem := TMenuItem.Create( popupQueryLoad );
menuitem.Caption := '-';
popupQueryLoad.Items.Add(menuitem);
// Recent files
j := 0;
for i:=0 to 19 do begin
sqlFilename := GetRegValue( 'SQLFile'+IntToStr(i), '' );
if sqlFilename = '' then
continue;
inc(j);
menuitem := TMenuItem.Create( popupQueryLoad );
menuitem.Caption := IntToStr(j) + ' ' + sqlFilename;
menuitem.OnClick := popupQueryLoadClick;
popupQueryLoad.Items.Add(menuitem);
end;
// Separator + "Remove absent files"
menuitem := TMenuItem.Create( popupQueryLoad );
menuitem.Caption := '-';
popupQueryLoad.Items.Add(menuitem);
menuitem := TMenuItem.Create( popupQueryLoad );
menuitem.Caption := 'Remove absent files';
menuitem.OnClick := PopupQueryLoadRemoveAbsentFiles;
popupQueryLoad.Items.Add(menuitem);
end;
procedure TMainform.PopupQueryLoadRemoveAbsentFiles( sender: TObject );
begin
AddOrRemoveFromQueryLoadHistory( '', false, true );
FillPopupQueryLoad;
end;
procedure TMainform.popupQueryLoadClick( sender: TObject );
var
filename : String;
p : Integer;
begin
// Click on the popupQueryLoad
filename := (Sender as TMenuItem).Caption;
if Pos( '\', filename ) = 0 then
begin // assuming we load a snippet
filename := DirnameSnippets + filename + '.sql';
end
else
begin // assuming we load a file from the recent-list
p := Pos( ' ', filename ) + 1;
filename := Copy(filename, p, Length(filename));
end;
filename := Stringreplace(filename, '&', '', [rfReplaceAll]);
QueryLoad( filename );
end;
procedure TMainform.AddOrRemoveFromQueryLoadHistory( filename: String; AddIt: Boolean = true; CheckIfFileExists: Boolean = true );
var
i : Integer;
Values, newfilelist : TStringList;
savedfilename : String;
begin
// Add or remove filename to/from history, avoiding duplicates
newfilelist := TStringList.create;
Values := TStringList.create;
OpenRegistry;
MainReg.GetValueNames( Values );
// Add new filename
if AddIt then
newfilelist.Add( filename );
// Add all other filenames
for i:=0 to Values.Count-1 do begin
if Pos( 'SQLFile', Values[i] ) <> 1 then
continue;
savedfilename := GetRegValue( Values[i], '' );
MainReg.DeleteValue( Values[i] );
if CheckIfFileExists and (not FileExists( savedfilename )) then
continue;
if (savedfilename <> filename) and (newfilelist.IndexOf(savedfilename)=-1) then
newfilelist.add( savedfilename );
end;
// Save new list
for i := 0 to newfilelist.Count-1 do begin
if i >= 20 then
break;
MainReg.WriteString( 'SQLFile'+IntToStr(i), newfilelist[i] );
end;
end;
{**
Change default delimiter for SQL execution
}
procedure TMainForm.actSetDelimiterExecute(Sender: TObject);
var
newVal: String;
ok: Boolean;
begin
// Use a while loop to redisplay the input dialog after setting an invalid value
ok := False;
while not ok do begin
newVal := delimiter;
if InputQuery('Set delimiter', 'SQL statement delimiter (default is ";"):', newVal) then try
// Set new value
Delimiter := newVal;
ok := True;
except on E:Exception do
MessageDlg(E.Message, mtError, [mbOK], 0);
end else // Cancel clicked
ok := True;
end;
end;
{**
Validates and sets the Delimiter property plus updates the hint on actSetDelimiter
}
procedure TMainForm.SetDelimiter(Value: String);
var
ErrMsg: String;
begin
ErrMsg := '';
Value := Trim(Value);
// Test for empty delimiter.
if Value = '' then ErrMsg := 'DELIMITER must be followed by a non-comment character or string';
// Disallow backslash, because the MySQL CLI does so for some reason.
// Then again, is there any reason to be bug-per-bug compatible with some random SQL parser?
if Pos('\', Value) > 0 then ErrMsg := 'Backslash disallowed in DELIMITER (because the MySQL CLI does not accept it)';
// Disallow stuff which would be negated by the comment parsing logic.
if
(Pos('/*', Value) > 0) or
(Pos('--', Value) > 0) or
(Pos('#', Value) > 0)
then ErrMsg := 'Start-of-comment tokens disallowed in DELIMITER (because it would be ignored)';
// Disallow stuff which would be negated by the SQL parser (and could slightly confuse it, if at end-of-string).
if
(Pos('''', Value) > 0) or
(Pos('`', Value) > 0) or
(Pos('"', Value) > 0)
then ErrMsg := 'String literal markers disallowed in DELIMITER (because it would be ignored)';
// Reset an invalid delimiter loaded from registry at startup to the default value
if (ErrMsg <> '') and (Delimiter = '') then begin
Value := DEFAULT_DELIMITER;
ErrMsg := '';
end;
// Raise or set it
if ErrMsg <> '' then begin
ErrMsg := Format('Invalid delimiter %s: %s.', [Value, ErrMsg]);
LogSQL(ErrMsg);
Raise Exception.Create(ErrMsg);
end else begin
FDelimiter := Value;
LogSQL(Format('Delimiter changed to %s.', [Delimiter]));
actSetDelimiter.Hint := actSetDelimiter.Caption + ' (current value: '+Delimiter+')';
end;
end;
procedure TMainForm.actApplyFilterExecute(Sender: TObject);
var
i, nr: Integer;
OldNumbers, Filters: TStringList;
val: String;
begin
// If filter box is empty but filter generator box not, most users expect
// the filter to be auto generated on button click
if (SynMemoFilter.GetTextLen = 0) and (editFilterSearch.Text <> '') then
editFilterSearchChange(editFilterSearch);
if SynMemoFilter.GetTextLen > 0 then begin
// Recreate recent filters list
Filters := TStringList.Create;
OldNumbers := TStringList.Create;
Filters.Add(Trim(SynMemoFilter.Text));
MainReg.OpenKey(GetRegKeyTable+'\'+REGNAME_FILTERS, True);
MainReg.GetValueNames(OldNumbers);
OldNumbers.CustomSort(CompareNumbers);
// Add old filters
for i := 0 to OldNumbers.Count - 1 do begin
nr := MakeInt(OldNumbers[i]);
if nr = 0 then continue; // Not a valid entry, ignore that
val := MainReg.ReadString(OldNumbers[i]);
if Filters.IndexOf(val) = -1 then
Filters.Add(val);
MainReg.DeleteValue(OldNumbers[i]);
end;
for i := 1 to Filters.Count do begin
MainReg.WriteString(IntToStr(i), Filters[i-1]);
// Avoid too much registry spam with mega old filters
if i = 20 then break;
end;
FreeAndNil(OldNumbers);
FreeAndNil(Filters);
end;
InvalidateVT(DataGrid, VTREE_NOTLOADED_PURGECACHE, False);
end;
procedure TMainForm.actDataFirstExecute(Sender: TObject);
var
Node: PVirtualNode;
begin
Node := DataGrid.GetFirst;
if Assigned(Node) then begin
DataGrid.ClearSelection;
DataGrid.FocusedNode := Node;
DataGrid.Selected[Node] := True;
end;
end;
procedure TMainForm.actDataInsertExecute(Sender: TObject);
begin
DataGridInsertRow(nil);
end;
procedure TMainForm.actDataDuplicateRowExecute(Sender: TObject);
begin
DataGridEnsureFullRow(DataGrid, DataGrid.FocusedNode);
DataGridInsertRow(DataGrid.FocusedNode);
end;
procedure TMainForm.actDataLastExecute(Sender: TObject);
var
Node: PVirtualNode;
begin
// Be sure to have all rows
if DatagridWantedRowCount < prefGridRowcountMax then
actDataShowAll.Execute;
Node := DataGrid.GetLast;
if Assigned(Node) then
SelectNode(DataGrid, Node);
end;
procedure TMainForm.actDataPostChangesExecute(Sender: TObject);
begin
DataGridPostUpdateOrInsert(Datagrid.FocusedNode);
end;
procedure TMainForm.actRemoveFilterExecute(Sender: TObject);
begin
actClearFilterEditor.Execute;
InvalidateVT(DataGrid, VTREE_NOTLOADED_PURGECACHE, False);
end;
procedure TMainForm.actDataCancelChangesExecute(Sender: TObject);
begin
DataGridCancel(Sender);
end;
procedure TMainForm.actSelectTreeBackgroundExecute(Sender: TObject);
var
cs: TColorSelect;
begin
// Select database tree background color
cs := TColorSelect.Create(Self);
cs.Dialog.Color := DBtree.Color;
if cs.Execute then begin
DBtree.Color := cs.Dialog.Color;
OpenRegistry(SessionName);
MainReg.WriteInteger(REGNAME_TREEBACKGROUND, cs.Dialog.Color);
end;
end;
{**
Add a SQL-command or comment to SynMemoSQLLog
}
procedure TMainForm.LogSQL(Msg: String; Category: TMySQLLogCategory=lcInfo);
var
snip, IsSQL: Boolean;
begin
if csDestroying in ComponentState then
Exit;
// Log only wanted events
case Category of
lcError: if not prefLogErrors then Exit;
lcUserFiredSQL: if not prefLogUserSQL then Exit;
lcSQL: if not prefLogSQL then Exit;
lcInfo: if not prefLogInfos then Exit;
lcDebug: if not prefLogDebug then Exit;
end;
// Shorten very long messages
snip := (prefLogSqlWidth > 0) and (Length(Msg) > prefLogSqlWidth);
IsSQL := Category in [lcSQL, lcUserFiredSQL];
if snip then begin
Msg :=
Copy(Msg, 0, prefLogSqlWidth) +
'/* large SQL query, snipped at ' +
FormatNumber(prefLogSqlWidth) +
' characters */';
end else if (not snip) and IsSQL then
Msg := Msg + Delimiter;
if not IsSQL then
Msg := '/* ' + Msg + ' */';
Msg := StringReplace(Msg, #9, ' ', [rfReplaceAll]);
Msg := StringReplace(Msg, #10, ' ', [rfReplaceAll]);
Msg := StringReplace(Msg, #13, ' ', [rfReplaceAll]);
Msg := StringReplace(Msg, ' ', ' ', [rfReplaceAll]);
SynMemoSQLLog.Lines.Add(Msg);
TrimSQLLog;
// Scroll to last line and repaint
SynMemoSQLLog.GotoLineAndCenter(SynMemoSQLLog.Lines.Count);
SynMemoSQLLog.Repaint;
// Log to file?
if prefLogToFile then
try
WriteLn(FileHandleSessionLog, Format('/* %s */ %s', [DateTimeToStr(Now), msg]));
except
DeactivateFileLogging;
MessageDlg('Error writing to session log file:'+CRLF+FileNameSessionLog+CRLF+CRLF+'Logging is disabled now.', mtError, [mbOK], 0);
end;
end;
{**
Delete first line(s) in SQL log and adjust LineNumberStart in gutter
Called by LogSQL and preferences dialog
}
procedure TMainForm.TrimSQLLog;
var
i : Integer;
begin
i := 0;
while SynMemoSQLLog.Lines.Count > prefLogsqlnum do
begin
SynMemoSQLLog.Lines.Delete(0);
inc(i);
end;
// Increase first displayed number in gutter so it doesn't lie about the log entries
if i > 0 then
SynMemoSQLLog.Gutter.LineNumberStart := SynMemoSQLLog.Gutter.LineNumberStart + i;
end;
procedure TMainForm.actDataShowNextExecute(Sender: TObject);
var
OldRowCount: Int64;
begin
// Show next X rows in datagrid
OldRowCount := DatagridWantedRowCount;
Inc(DatagridWantedRowCount, prefGridRowcountStep);
DataGridWantedRowCount := Min(DataGridWantedRowCount, prefGridRowcountMax);
InvalidateVT(DataGrid, VTREE_NOTLOADED, True);
SelectNode(DataGrid, OldRowCount);
end;
procedure TMainForm.actDataShowAllExecute(Sender: TObject);
begin
// Remove LIMIT clause
DatagridWantedRowCount := prefGridRowcountMax;
InvalidateVT(DataGrid, VTREE_NOTLOADED, True);
end;
function TMainForm.DataGridEnsureFullRow(Grid: TVirtualStringTree; Node: PVirtualNode): Boolean;
var
i: Integer;
Row: PGridRow;
Select: String;
Data: TMySQLQuery;
begin
// Load remaining data on a partially loaded row in data grid
if Grid <> DataGrid then begin
Result := True;
Exit;
end;
Row := @DataGridResult.Rows[Node.Index];
if not DataGridRowHasFullData(Node) then begin
Select := 'SELECT ';
for i:=0 to Length(DataGridResult.Columns)-1 do
Select := Select + mask(DataGridResult.Columns[i].Name) + ', ';
Delete(Select, Length(Select)-1, 2);
Select := Select +
' FROM '+mask(SelectedTable.Name) +
' WHERE '+GetWhereClause(Row, @DataGridResult.Columns) +
' LIMIT 1';
try
Data := Connection.GetResults(Select);
if Data.RecordCount = 0 then
raise Exception.Create('Unable to find row.');
for i:=0 to Length(DataGridResult.Columns)-1 do begin
case DataGridResult.Columns[i].DatatypeCat of
dtcInteger, dtcReal: Row.Cells[i].Text := FormatNumber(Data.Col(i), False);
dtcBinary, dtcSpatial: Row.Cells[i].Text := GetBlobContent(Data, i);
else Row.Cells[i].Text := Data.Col(i);
end;
Row.Cells[i].IsNull := Data.IsNull(i);
end;
Row.HasFullData := True;
except On E:Exception do
MessageDlg(E.Message, mtError, [mbOK], 0);
end;
end;
Result := Row.HasFullData;
end;
procedure TMainForm.DataGridEnsureFullRows(Grid: TVirtualStringTree; SelectedOnly: Boolean);
var
Node: PVirtualNode;
begin
// Load remaining data of all grid rows
if Grid <> DataGrid then
Exit;
if SelectedOnly then
Node := Grid.GetFirstSelected
else
Node := Grid.GetFirst;
while Assigned(Node) do begin
if not DataGridRowHasFullData(Node) then begin
DataGridFullRowMode := True;
InvalidateVT(Grid, VTREE_NOTLOADED_PURGECACHE, True);
break;
end;
if SelectedOnly then
Node := Grid.GetNextSelected(Node)
else
Node := Grid.GetNext(Node);
end;
end;
function TMainForm.DataGridRowHasFullData(Node: PVirtualNode): Boolean;
var
i: Integer;
HasFullData: Boolean;
Row: PGridRow;
begin
Row := @DataGridResult.Rows[Node.Index];
if not Row.HasFullData then begin
HasFullData := True;
for i:=0 to Length(DataGridResult.Columns)-1 do begin
HasFullData := Length(Row.Cells[i].Text) < GRIDMAXDATA;
if not HasFullData then
break;
end;
Row.HasFullData := HasFullData;
end;
Result := Row.HasFullData;
end;
procedure TMainForm.DataGridBeforePaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas);
var
vt: TVirtualStringTree;
Data: TMySQLQuery;
Select: String;
RefreshingData, IsKeyColumn: Boolean;
i, j, Offset, ColLen, ColWidth: Integer;
KeyCols, ColWidths: TStringList;
WantedColumns: TTableColumnList;
Cell: PGridCell;
c: TTableColumn;
procedure InitColumn(idx: Integer; TblCol: TTableColumn);
var
k: Integer;
Col: TVirtualTreeColumn;
begin
SetLength(DataGridResult.Columns, idx+1);
DataGridResult.Columns[idx].Name := TblCol.Name;
col := vt.Header.Columns.Add;
col.Text := TblCol.Name;
col.Hint := TblCol.Comment;
col.Options := col.Options + [coSmartResize];
if DatagridHiddenColumns.IndexOf(TblCol.Name) > -1 then
col.Options := col.Options - [coVisible];
// Sorting color and title image
for k:=0 to Length(DataGridSortColumns)-1 do begin
if DataGridSortColumns[k].ColumnName = TblCol.Name then begin
col.Color := ColorAdjustBrightness(col.Color, COLORSHIFT_SORTCOLUMNS);
case DataGridSortColumns[k].SortDirection of
ORDER_ASC: col.ImageIndex := 109;
ORDER_DESC: col.ImageIndex := 110;
end;
end;
end;
if col.ImageIndex = -1 then begin
for k:=0 to SelectedTableKeys.Count-1 do begin
if SelectedTableKeys[k].Columns.IndexOf(TblCol.Name) > -1 then begin
col.ImageIndex := GetIndexIcon(SelectedTableKeys[k].IndexType);
break;
end;
end;
end;
// Data type
DataGridResult.Columns[idx].DatatypeCat := TblCol.DataType.Category;
DataGridResult.Columns[idx].Datatype := TblCol.DataType.Index;
col.Alignment := taLeftJustify;
case DataGridResult.Columns[idx].DatatypeCat of
dtcInteger, dtcReal: col.Alignment := taRightJustify;
dtcText: begin
if TblCol.LengthSet <> '' then
DataGridResult.Columns[idx].MaxLength := MakeInt(TblCol.LengthSet)
else case TblCol.DataType.Index of
// 255 is the width in bytes. If characters that use multiple bytes are
// contained, the width in characters is decreased below this number.
dtTinyText: DataGridResult.Columns[idx].MaxLength := 255;
dtText: DataGridResult.Columns[idx].MaxLength := 65535;
dtMediumText: DataGridResult.Columns[idx].MaxLength := 16777215;
dtLongText: DataGridResult.Columns[idx].MaxLength := 4294967295;
end;
end;
dtcIntegerNamed: begin
DataGridResult.Columns[idx].ValueList := TStringList.Create;
DataGridResult.Columns[idx].ValueList.QuoteChar := '''';
DataGridResult.Columns[idx].ValueList.Delimiter := ',';
DataGridResult.Columns[idx].ValueList.DelimitedText := TblCol.LengthSet;
end;
dtcSetNamed: begin
DataGridResult.Columns[idx].ValueList := TStringList.Create;
DataGridResult.Columns[idx].ValueList.QuoteChar := '''';
DataGridResult.Columns[idx].ValueList.Delimiter := ',';
DataGridResult.Columns[idx].ValueList.DelimitedText := TblCol.LengthSet;
end;
else DataGridResult.Columns[idx].MaxLength := MaxInt; // Fallback for unknown column types
end;
DataGridResult.Columns[idx].IsPriPart := Data.ColIsPrimaryKeyPart(idx);
end;
begin
// Load data into data tab grid
vt := Sender as TVirtualStringTree;
if vt.Tag = VTREE_LOADED then
Exit;
Screen.Cursor := crHourglass;
// No data for routines
if SelectedTableColumns.Count = 0 then begin
vt.Enabled := False;
pnlDataTop.Enabled := False;
pnlFilter.Enabled := False;
lblSorryNoData.Parent := DataGrid;
end else begin
vt.Enabled := True;
pnlDataTop.Enabled := True;
pnlFilter.Enabled := True;
lblSorryNoData.Parent := tabData;
// Indicates whether the current table data is just refreshed or if we're in another table
RefreshingData := (ActiveDatabase = DataGridDB) and (SelectedTable.Name = DataGridTable);
// Load last view settings
HandleDataGridAttributes(RefreshingData);
DataGridDB := SelectedTable.Database;
DataGridTable := SelectedTable.Name;
Select := 'SELECT ';
// Ensure key columns are included to enable editing
KeyCols := GetKeyColumns;
WantedColumns := TTableColumnList.Create(False);
for i:=0 to SelectedTableColumns.Count-1 do begin
c := SelectedTableColumns[i];
IsKeyColumn := KeyCols.IndexOf(c.Name) > -1;
ColLen := MakeInt(c.LengthSet);
if (DatagridHiddenColumns.IndexOf(c.Name) = -1)
or (IsKeyColumn)
or (KeyCols.Count = 0)
then begin
if not DataGridFullRowMode
and (KeyCols.Count > 0) // We need a sufficient key to be able to load remaining row data
and (c.DataType.Category in [dtcText, dtcBinary])
and (not IsKeyColumn) // We need full length of any key column, so DataGridLoadFullRow() has the chance to fetch the right row
and ((ColLen > GRIDMAXDATA) or (ColLen = 0)) // No need to blow SQL with LEFT() if column is shorter anyway
then
Select := Select + ' LEFT(' + Mask(c.Name) + ', ' + IntToStr(GRIDMAXDATA) + '), '
else
Select := Select + ' ' + Mask(c.Name) + ', ';
WantedColumns.Add(c);
end;
end;
// Cut last comma
Delete(Select, Length(Select)-1, 2);
// Include db name for cases in which dbtree is switching databases and pending updates are in process
Select := Select + ' FROM '+mask(ActiveDatabase)+'.'+mask(SelectedTable.Name);
// Signal for the user if we hide some columns
if WantedColumns.Count = SelectedTableColumns.Count then
tbtnDataColumns.ImageIndex := 107
else
tbtnDataColumns.ImageIndex := 108;
// Append WHERE clause
if SynMemoFilter.GetTextLen > 0 then begin
Select := Select + ' WHERE ' + SynMemoFilter.Text;
tbtnDataFilter.ImageIndex := 108;
end else
tbtnDataFilter.ImageIndex := 107;
// Append ORDER clause
if Length(DataGridSortColumns) > 0 then begin
Select := Select + ' ORDER BY ' + ComposeOrderClause(DataGridSortColumns);
tbtnDataSorting.ImageIndex := 108;
end else
tbtnDataSorting.ImageIndex := 107;
// Append LIMIT clause
if RefreshingData and (vt.Tag <> VTREE_NOTLOADED_PURGECACHE) then
Offset := Length(DataGridResult.Rows)
else
Offset := 0;
Select := Select + ' LIMIT '+IntToStr(Offset)+', '+IntToStr(DatagridWantedRowCount-Offset);
try
ShowStatusMsg('Fetching rows ...');
Data := Connection.GetResults(Select);
except
// Wrong WHERE clause in most cases
On E:Exception do
MessageDlg(E.Message, mtError, [mbOK], 0);
end;
if Assigned(Data) then begin
editFilterVT.Clear;
TimerFilterVT.OnTimer(Sender);
// Set up grid column headers
ShowStatusMsg('Setting up columns ...');
ColWidths := TStringList.Create;
for i:=0 to vt.Header.Columns.Count-1 do
ColWidths.Values[vt.Header.Columns[i].Text] := IntToStr(vt.Header.Columns[i].Width);
SetLength(DataGridResult.Columns, Data.ColumnCount);
vt.Header.Columns.BeginUpdate;
vt.Header.Columns.Clear;
for i:=0 to WantedColumns.Count-1 do
InitColumn(i, WantedColumns[i]);
// Set up grid rows and data array
ShowStatusMsg('Copying rows to internal structure ...');
vt.BeginUpdate;
SetLength(DataGridResult.Rows, Offset+Data.RecordCount);
for i:=Offset to Offset+Data.RecordCount-1 do begin
DataGridResult.Rows[i].HasFullData := DataGridFullRowMode or (KeyCols.Count = 0);
for j:=0 to Length(DataGridResult.Columns)-1 do begin
SetLength(DataGridResult.Rows[i].Cells, Data.ColumnCount);
Cell := @DataGridResult.Rows[i].Cells[j];
case DataGridResult.Columns[j].DatatypeCat of
dtcInteger, dtcReal: Cell.Text := FormatNumber(Data.Col(j), False);
dtcBinary, dtcSpatial: Cell.Text := GetBlobContent(Data, j);
else Cell.Text := Data.Col(j);
end;
Cell.IsNull := Data.IsNull(j);
end;
Data.Next;
end;
ShowStatusMsg('Freeing memory ...');
FreeAndNil(Data);
vt.RootNodeCount := Length(DataGridResult.Rows);
// Autoset or restore column width
for i:=0 to vt.Header.Columns.Count-1 do begin
ColWidth := 0;
if RefreshingData then
ColWidth := StrToIntDef(ColWidths.Values[vt.Header.Columns[i].Text], ColWidth);
if ColWidth > 0 then
vt.Header.Columns[i].Width := ColWidth
else
AutoCalcColWidth(vt, i);
end;
ColWidths.Free;
vt.Header.Columns.EndUpdate;
vt.EndUpdate;
// Do not steel filter while writing filters
if not SynMemoFilter.Focused then
vt.SetFocus;
if not RefreshingData then begin
// Scroll to top left if switched to another table
vt.OffsetXY := Point(0, 0);
end;
if vt.RootNodeCount > DataGridFocusedNodeIndex then begin
SelectNode(vt, DataGridFocusedNodeIndex);
for i:=0 to vt.Header.Columns.Count-1 do begin
if vt.Header.Columns[i].Text = DataGridFocusedColumnName then begin
vt.FocusedColumn := i;
break;
end;
end;
end;
DisplayRowCountStats;
actDataShowNext.Enabled := (vt.RootNodeCount = DatagridWantedRowCount) and (DatagridWantedRowCount < prefGridRowcountMax);
actDataShowAll.Enabled := actDataShowNext.Enabled;
EnumerateRecentFilters;
if Integer(vt.RootNodeCount) = prefGridRowcountMax then
LogSQL('Browsing is currently limited to a maximum of '+FormatNumber(prefGridRowcountMax)+' rows. To see more rows, increase this maximum in Tools > Preferences > Data .', lcInfo);
end;
end;
vt.Tag := VTREE_LOADED;
DataGridFullRowMode := False;
Screen.Cursor := crDefault;
ShowStatusMsg(STATUS_MSG_READY);
end;
{***
Calculate + display total rowcount and found rows matching to filter
in data-tab
}
procedure TMainForm.DisplayRowCountStats;
var
DBObject: TDBObject;
IsFiltered, IsLimited: Boolean;
cap: String;
RowsTotal: Int64;
begin
DBObject := SelectedTable;
cap := ActiveDatabase + '.' + DBObject.Name;
IsLimited := DataGridWantedRowCount <= Datagrid.RootNodeCount;
IsFiltered := SynMemoFilter.GetTextLen > 0;
if DBObject.NodeType = lntTable then begin
if (not IsLimited) and (not IsFiltered) then
RowsTotal := DataGrid.RootNodeCount // No need to fetch via SHOW TABLE STATUS
else
RowsTotal := MakeInt(Connection.GetVar('SHOW TABLE STATUS LIKE '+esc(DBObject.Name), 'Rows'));
if RowsTotal > -1 then begin
cap := cap + ': ' + FormatNumber(RowsTotal) + ' rows total';
if DBObject.Engine = 'InnoDB' then
cap := cap + ' (approximately)';
// Display either LIMIT or WHERE effect, not both at the same time
if IsLimited then
cap := cap + ', limited to ' + FormatNumber(Datagrid.RootNodeCount)
else if IsFiltered then begin
if Datagrid.RootNodeCount = RowsTotal then
cap := cap + ', all rows match to filter'
else
cap := cap + ', ' + FormatNumber(Datagrid.RootNodeCount) + ' rows match to filter';
end;
end;
end;
lblDataTop.Caption := cap;
end;
procedure TMainForm.AnyGridInitNode(Sender: TBaseVirtualTree; ParentNode,
Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
begin
// Display multiline grid rows
if prefGridRowsLineCount = DEFAULT_GRIDROWSLINECOUNT then
Exclude(Node.States, vsMultiLine)
else
Include(Node.States, vsMultiLine);
end;
{***
Occurs when active tab has changed.
}
procedure TMainForm.PageControlMainChange(Sender: TObject);
var
tab: TTabSheet;
begin
tab := PageControlMain.ActivePage;
// Move focus to relevant controls in order for them to receive keyboard events.
// Do this only if the user clicked the new tab. Not on automatic tab changes.
if Sender = PageControlMain then begin
if tab = tabHost then PageControlHostChange(Sender)
else if tab = tabDatabase then ListTables.SetFocus
else if tab = tabData then begin
if DataGrid.CanFocus then
DataGrid.SetFocus;
end else if IsQueryTab(tab.PageIndex, True) then begin
ActiveQueryMemo.SetFocus;
ActiveQueryMemo.WordWrap := actQueryWordWrap.Checked;
SynMemoQueryStatusChange(ActiveQueryMemo, []);
end;
end;
// Filter panel has one text per tab, which we need to update
UpdateFilterPanel(Sender);
// Ensure controls are in a valid state
ValidateControls(Sender);
FixQueryTabCloseButtons;
end;
procedure TMainForm.PageControlHostChange(Sender: TObject);
var
tab: TTabSheet;
list: TBaseVirtualTree;
begin
tab := PageControlHost.ActivePage;
if tab = tabDatabases then list := ListDatabases
else if tab = tabVariables then list := ListVariables
else if tab = tabStatus then list := ListStatus
else if tab = tabProcesslist then list := ListProcesses
else if tab = tabCommandStats then list := ListCommandStats
else Exit; // Silence compiler warning
list.SetFocus;
UpdateFilterPanel(Sender);
end;
procedure TMainForm.ListTablesBeforePaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas);
var
i, NumObj: Integer;
Obj: TDBObject;
Objects: TDBObjectList;
NumObjects: TStringList;
Msg: String;
vt: TVirtualStringTree;
begin
// DB-Properties
vt := Sender as TVirtualStringTree;
if vt.Tag = VTREE_LOADED then
Exit;
Screen.Cursor := crHourGlass;
ShowStatusMsg( 'Displaying objects from "' + ActiveDatabase + '" ...' );
Objects := Connection.GetDBObjects(ActiveDatabase, vt.Tag = VTREE_NOTLOADED_PURGECACHE);
ListTables.BeginUpdate;
ListTables.RootNodeCount := Objects.Count;
ListTables.ReinitChildren(nil, false);
ListTables.EndUpdate;
vt.Tag := VTREE_LOADED;
NumObjects := TStringList.Create;
DBObjectsMaxSize := 1;
DBObjectsMaxRows := 1;
for i:=0 to Objects.Count-1 do begin
Obj := Objects[i];
NumObj := StrToIntDef(NumObjects.Values[Obj.ObjType], 0);
Inc(NumObj);
NumObjects.Values[Obj.ObjType] := IntToStr(NumObj);
if Obj.Size > DBObjectsMaxSize then DBObjectsMaxSize := Obj.Size;
if Obj.Rows > DBObjectsMaxRows then DBObjectsMaxRows := Obj.Rows;
end;
Msg := ActiveDatabase + ': ' + FormatNumber(Objects.Count) + ' ';
if NumObjects.Count = 1 then
Msg := Msg + LowerCase(NumObjects.Names[0])
else
Msg := Msg + 'object';
if Objects.Count <> 1 then Msg := Msg + 's';
if (NumObjects.Count > 1) and (Objects.Count > 0) then begin
Msg := Msg + ' (';
for i:=0 to NumObjects.Count-1 do begin
NumObj := StrToIntDef(NumObjects.ValueFromIndex[i], 0);
if NumObj = 0 then
Continue;
Msg := Msg + FormatNumber(NumObj) + ' ' + LowerCase(NumObjects.Names[i]);
if NumObj <> 1 then Msg := Msg + 's';
Msg := Msg + ', ';
end;
Delete(Msg, Length(Msg)-1, 2);
Msg := Msg + ')';
end;
ShowStatusMsg(Msg, 0);
ShowStatusMsg(STATUS_MSG_READY);
Screen.Cursor := crDefault;
// Ensure tree db node displays its chidren initialized
DBtree.ReinitChildren(FindDBNode(ActiveDatabase), False);
ValidateControls(Self);
end;
procedure TMainForm.ListTablesGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode;
Kind: TVTImageKind; Column: TColumnIndex; var Ghosted: Boolean; var ImageIndex: Integer);
var
Obj: PDBObject;
begin
if not (Kind in [ikNormal, ikSelected]) then
Exit;
if Column <> (Sender as TVirtualStringTree).Header.MainColumn then
Exit;
Obj := Sender.GetNodeData(Node);
ImageIndex := Obj.ImageIndex;
end;
procedure TMainForm.ListTablesGetNodeDataSize(Sender: TBaseVirtualTree;
var NodeDataSize: Integer);
begin
NodeDataSize := SizeOf(TDBObject);
end;
procedure TMainForm.ListTablesGetText(Sender: TBaseVirtualTree;
Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType;
var CellText: string);
var
Obj: PDBObject;
begin
Obj := Sender.GetNodeData(Node);
CellText := '';
case Column of
0: CellText := Obj.Name;
1: if Obj.Rows > -1 then CellText := FormatNumber(Obj.Rows);
2: if Obj.Size > -1 then CellText := FormatByteNumber(Obj.Size);
3: if Obj.Created <> 0 then CellText := DateTimeToStr(Obj.Created);
4: if Obj.Updated <> 0 then CellText := DateTimeToStr(Obj.Updated);
5: CellText := Obj.Engine;
6: CellText := Obj.Comment;
7: if Obj.Version > -1 then CellText := IntToStr(Obj.Version);
8: CellText := Obj.RowFormat;
9: if Obj.AvgRowLen > -1 then CellText := FormatByteNumber(Obj.AvgRowLen);
10: if Obj.MaxDataLen > -1 then CellText := FormatByteNumber(Obj.MaxDataLen);
11: if Obj.IndexLen > -1 then CellText := FormatByteNumber(Obj.IndexLen);
12: if Obj.DataFree > -1 then CellText := FormatByteNumber(Obj.DataFree);
13: if Obj.AutoInc > -1 then CellText := FormatNumber(Obj.AutoInc);
14: if Obj.LastChecked <> 0 then CellText := DateTimeToStr(Obj.LastChecked);
15: CellText := Obj.Collation;
16: if Obj.Checksum > -1 then CellText := IntToStr(Obj.Checksum);
17: CellText := Obj.CreateOptions;
18: CellText := Obj.ObjType;
end;
end;
procedure TMainForm.ListTablesInitNode(Sender: TBaseVirtualTree; ParentNode,
Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
var
Obj: PDBObject;
Objects: TDBObjectList;
begin
Obj := Sender.GetNodeData(Node);
Objects := Connection.GetDBObjects(ActiveDatabase);
Obj^ := Objects[Node.Index];
end;
{***
Selection in ListTables is changing
}
procedure TMainForm.ListTablesChange(Sender: TBaseVirtualTree; Node:
PVirtualNode);
begin
ValidateControls(Sender);
end;
{***
Enable/disable various buttons and menu items.
Invoked when
- active sheet changes
- highlighted database changes
...
}
procedure TMainForm.ValidateControls(Sender: TObject);
var
inDataGrid, inDataTab, inDataOrQueryTab, inDataOrQueryTabNotEmpty: Boolean;
SelectedNodes: TNodeArray;
begin
inDataTab := PageControlMain.ActivePage = tabData;
inDataGrid := ActiveControl = DataGrid;
inDataOrQueryTab := (PageControlMain.ActivePage = tabData) or QueryTabActive;
inDataOrQueryTabNotEmpty := inDataOrQueryTab and (ActiveGrid.RootNodeCount > 0);
SelectedNodes := ListTables.GetSortedSelection(False);
actSQLhelp.Enabled := Connection.ServerVersionInt >= 40100;
actImportCSV.Enabled := Connection.ServerVersionInt >= 32206;
// Data tab - if query results are made editable, these will need
// to be changed to look at which tab is focused.
actDataInsert.Enabled := inDataGrid;
actDataDuplicateRow.Enabled := inDataGrid and Assigned(ActiveGrid.FocusedNode);
actDataDelete.Enabled := inDataGrid and (DataGrid.SelectedCount > 0);
actDataFirst.Enabled := inDataGrid;
actDataLast.Enabled := inDataGrid;
actDataPostChanges.Enabled := inDataGrid and DataGridHasChanges;
actDataCancelChanges.Enabled := inDataGrid and DataGridHasChanges;
if (not inDataTab) and DataGrid.IsEditing then
DataGrid.EndEditNode;
// Activate export-options if we're on Data- or Query-tab
actCopyAsCSV.Enabled := inDataOrQueryTabNotEmpty;
actCopyAsHTML.Enabled := inDataOrQueryTabNotEmpty;
actCopyAsXML.Enabled := inDataOrQueryTabNotEmpty;
actCopyAsSQL.Enabled := inDataOrQueryTabNotEmpty;
actExportData.Enabled := inDataOrQueryTabNotEmpty;
actHTMLView.Enabled := inDataOrQueryTabNotEmpty and Assigned(ActiveGrid.FocusedNode);
setNull1.Enabled := inDataGrid and Assigned(DataGrid.FocusedNode);
// Query tab
// Manually invoke OnChange event of tabset to fill helper list with data
if QueryTabActive then RefreshQueryHelpers;
ValidateQueryControls(Sender);
UpdateLineCharPanel;
end;
procedure TMainForm.RefreshQueryHelpers;
var
dummy: Boolean;
begin
dummy := True;
ActiveQueryTabset.OnChange(Self, ActiveQueryTabset.TabIndex, dummy);
end;
procedure TMainForm.ValidateQueryControls(Sender: TObject);
var
NotEmpty, HasSelection: Boolean;
Tab: TQueryTab;
cap: String;
InQueryTab: Boolean;
i: Integer;
begin
InQueryTab := QueryTabActive;
Tab := ActiveQueryTab;
NotEmpty := InQueryTab and (Tab.Memo.GetTextLen > 0);
HasSelection := InQueryTab and Tab.Memo.SelAvail;
actExecuteQuery.Enabled := InQueryTab and NotEmpty;
actExecuteSelection.Enabled := InQueryTab and HasSelection;
actExecuteLine.Enabled := InQueryTab and (Tab.Memo.LineText <> '');
actSaveSQLAs.Enabled := InQueryTab and NotEmpty;
actSaveSQL.Enabled := actSaveSQLAs.Enabled and Tab.Memo.Modified;
actSaveSQLselection.Enabled := InQueryTab and HasSelection;
actSaveSQLSnippet.Enabled := InQueryTab and NotEmpty;
actSaveSQLSelectionSnippet.Enabled := InQueryTab and HasSelection;
actQueryFind.Enabled := InQueryTab;
actQueryReplace.Enabled := InQueryTab;
actQueryFindAgain.Enabled := InQueryTab;
// We need a pressed button which somehow does not work in conjunction with Enabled=False
// actQueryStopOnErrors.Enabled := QueryTabActive;
actQueryWordWrap.Enabled := InQueryTab;
actClearQueryEditor.Enabled := InQueryTab and NotEmpty;
actSetDelimiter.Enabled := InQueryTab;
actCloseQueryTab.Enabled := IsQueryTab(PageControlMain.ActivePageIndex, False);
for i:=0 to QueryTabs.Count-1 do begin
cap := trim(QueryTabs[i].TabSheet.Caption);
if cap[Length(cap)] = '*' then
cap := copy(cap, 1, Length(cap)-1);
if QueryTabs[i].Memo.Modified then
cap := cap + '*';
if QueryTabs[i].TabSheet.Caption <> cap then
SetTabCaption(QueryTabs[i].TabSheet.PageIndex, cap);
end;
end;
procedure TMainForm.KillProcess(Sender: TObject);
var t : Boolean;
ProcessIDs : TStringList;
i : Integer;
begin
t := TimerRefresh.Enabled;
TimerRefresh.Enabled := false; // prevent av (ListProcesses.selected...)
ProcessIDs := GetVTCaptions( ListProcesses, True );
if MessageDlg('Kill '+inttostr(ProcessIDs.count)+' Process(es)?', mtConfirmation, [mbok,mbcancel], 0) = mrok then
begin
try
for i:=0 to ProcessIDs.Count-1 do begin
// Don't kill own process
if ProcessIDs[i] = IntToStr(Connection.ThreadId) then
LogSQL('Ignoring own process id '+ProcessIDs[i]+' when trying to kill it.')
else
Connection.Query('KILL '+ProcessIDs[i]);
end;
except
on E:Exception do
MessageDlg(E.Message, mtError, [mbOK], 0);
end;
InvalidateVT(ListProcesses, VTREE_NOTLOADED, True);
end;
TimerRefresh.Enabled := t; // re-enable autorefresh timer
end;
procedure TMainForm.ExecSQLClick(Sender: TObject; Selection: Boolean=false; CurrentLine: Boolean=false);
var
SQL: TStringList;
i, j, QueryCount: Integer;
SQLTime, SQLNetTime: Cardinal;
Results: TMySQLQuery;
ColName, Text, LB: String;
col: TVirtualTreeColumn;
ResultLabel: TLabel;
ActiveGridResult: TGridResult;
cap: String;
begin
ResultLabel := ActiveQueryTab.LabelResultInfo;
if CurrentLine then Text := ActiveQueryMemo.LineText
else if Selection then Text := ActiveQueryMemo.SelText
else Text := ActiveQueryMemo.Text;
// Give text back its original linebreaks if possible
case ActiveQueryTab.MemoLineBreaks of
lbsUnix: LB := LB_UNIX;
lbsMac: LB := LB_MAC;
lbsWide: LB := LB_WIDE;
end;
if LB <> '' then
Text := StringReplace(Text, CRLF, LB, [rfReplaceAll]);
ShowStatusMsg('Initializing SQL...');
SQL := parseSQL(Text);
if SQL.Count = 0 then begin
ResultLabel.Caption := '(nothing to do)';
Exit;
end;
ResultLabel.Caption := '';
FreeAndNil(Results);
actExecuteQuery.Enabled := false;
actExecuteSelection.Enabled := false;
EnableProgressBar(SQL.Count);
ShowStatusMsg('Executing SQL...');
SQLtime := 0;
SQLNetTime := 0;
QueryCount := 0;
Results := TMySQLQuery.Create(Self);
Results.Connection := Connection;
Results.LogCategory := lcUserFiredSQL;
for i:=0 to SQL.Count-1 do begin
ProgressBarStatus.StepIt;
ProgressBarStatus.Repaint;
try
// Immediately free results for all but last query
Results.SQL := SQL[i];
Results.StoreResult := i = SQL.Count-1;
Results.Execute;
Inc(SQLtime, Connection.LastQueryDuration);
Inc(SQLNetTime, Connection.LastQueryNetworkDuration);
Inc(QueryCount);
if Assigned(Results) and Results.HasResult then
ResultLabel.Caption := FormatNumber(Results.ColumnCount) +' column(s) x '+FormatNumber(Results.RecordCount) +' row(s) in last result set.'
else
ResultLabel.Caption := FormatNumber(Connection.RowsAffected) +' row(s) affected by last query.';
except
on E:Exception do begin
if actQueryStopOnErrors.Checked or (i = SQL.Count - 1) then begin
Screen.Cursor := crDefault;
MessageDlg( E.Message, mtError, [mbOK], 0 );
Break;
end;
end;
end;
end;
if ResultLabel.Caption <> '' then begin
cap := ' Duration for ';
cap := cap + IntToStr(QueryCount);
if QueryCount < SQL.Count then
cap := cap + ' of ' + IntToStr(SQL.Count);
if SQL.Count = 1 then
cap := cap + ' query'
else
cap := cap + ' queries';
cap := cap + ': '+FormatNumber(SQLTime/1000, 3) +' sec.';
if SQLNetTime > 0 then
cap := cap + ' (+ '+FormatNumber(SQLNetTime/1000, 3) +' sec. network)';
ResultLabel.Caption := ResultLabel.Caption + cap;
end;
// Avoid excessive GridHighlightChanged() when flicking controls.
ProgressBarStatus.Hide;
if Assigned(Results) and Results.HasResult then begin
editFilterVT.Clear;
TimerFilterVT.OnTimer(Sender);
ActiveGrid.BeginUpdate;
// Reset filter if filter panel was disabled
UpdateFilterPanel(Sender);
ActiveGrid.Header.Options := ActiveGrid.Header.Options + [hoVisible];
ActiveGrid.Header.Columns.BeginUpdate;
ActiveGrid.Header.Columns.Clear;
debug('mem: clearing and initializing query columns.');
ActiveGridResult := GridResult(ActiveGrid);
SetLength(ActiveGridResult.Columns, 0);
SetLength(ActiveGridResult.Columns, Results.ColumnCount);
for i:=0 to Results.ColumnCount-1 do begin
ColName := Results.ColumnNames[i];
col := ActiveGrid.Header.Columns.Add;
col.Text := ColName;
col.Options := col.Options - [coAllowClick];
ActiveGridResult.Columns[i].Name := ColName;
ActiveGridResult.Columns[i].DatatypeCat := Results.DataType(i).Category;
if ActiveGridResult.Columns[i].DatatypeCat in [dtcInteger, dtcReal] then
col.Alignment := taRightJustify;
ActiveGridResult.Columns[i].IsPriPart := Results.ColIsPrimaryKeyPart(i);
end;
debug('mem: query column initialization complete.');
debug('mem: clearing and initializing query rows (internal data).');
SetLength(ActiveGridResult.Rows, 0);
SetLength(ActiveGridResult.Rows, Results.RecordCount);
Results.First;
for i:=0 to Results.RecordCount-1 do begin
SetLength(ActiveGridResult.Rows[i].Cells, Results.ColumnCount);
for j:=0 to Results.ColumnCount-1 do begin
case ActiveGridResult.Columns[j].DatatypeCat of
dtcInteger, dtcReal: ActiveGridResult.Rows[i].Cells[j].Text := FormatNumber(Results.Col(j), False);
dtcBinary, dtcSpatial: ActiveGridResult.Rows[i].Cells[j].Text := GetBlobContent(Results, j);
else ActiveGridResult.Rows[i].Cells[j].Text := Results.Col(j);
end;
ActiveGridResult.Rows[i].Cells[j].IsNull := Results.IsNull(j);
end;
Results.Next;
end;
Results.Free;
debug('mem: initializing query rows (grid).');
ActiveGrid.RootNodeCount := Length(ActiveGridResult.Rows);
debug('mem: query row initialization complete.');
ActiveGrid.Header.Columns.EndUpdate;
ActiveGrid.ClearSelection;
ActiveGrid.OffsetXY := Point(0, 0);
for i:=0 to ActiveGrid.Header.Columns.Count-1 do
AutoCalcColWidth(ActiveGrid, i);
ActiveGrid.EndUpdate;
end;
// Ensure controls are in a valid state
ValidateControls(Sender);
Screen.Cursor := crDefault;
ShowStatusMsg( STATUS_MSG_READY );
end;
{ Proposal about to insert a String into synmemo }
procedure TMainForm.SynCompletionProposalCodeCompletion(Sender: TObject;
var Value: String; Shift: TShiftState; Index: Integer; EndToken: Char);
begin
(Sender as TSynCompletionProposal).Form.CurrentEditor.UndoList.AddGroupBreak;
end;
procedure TMainForm.SynCompletionProposalAfterCodeCompletion(Sender: TObject;
const Value: String; Shift: TShiftState; Index: Integer; EndToken: Char);
begin
(Sender as TSynCompletionProposal).Form.CurrentEditor.UndoList.AddGroupBreak;
end;
{ Proposal-Combobox pops up }
procedure TMainForm.SynCompletionProposalExecute(Kind: SynCompletionType;
Sender: TObject; var CurrentInput: String; var x, y: Integer;
var CanExecute: Boolean);
var
i,j : Integer;
Results : TMySQLQuery;
DBObjects : TDBObjectList;
sql, TableClauses: String;
Tables : TStringList;
tablename : String;
rx : TRegExpr;
PrevShortToken,
PrevLongToken,
Token : UnicodeString;
Start,
TokenTypeInt : Integer;
Attri : TSynHighlighterAttributes;
Proposal : TSynCompletionProposal;
Editor : TCustomSynEdit;
Queries : TStringList;
procedure addTable(Obj: TDBObject);
begin
Proposal.InsertList.Add(Obj.Name);
Proposal.ItemList.Add( Format(SYNCOMPLETION_PATTERN, [Obj.ImageIndex, LowerCase(Obj.ObjType), Obj.Name]) );
end;
procedure addColumns( tablename: String );
var
dbname : String;
Columns: TMySQLQuery;
begin
dbname := ActiveDatabase;
if Pos( '.', tablename ) > -1 then
begin
dbname := Copy( tablename, 0, Pos( '.', tablename )-1 );
tablename := Copy( tablename, Pos( '.', tablename )+1, Length(tablename) );
end;
// Do not mask db and table name to avoid double masking.
// Rely on what the user typed is already a valid masked/quoted identifier.
if dbname <> '' then
tablename := dbname + '.' + tablename;
try
Columns := Connection.GetResults('SHOW COLUMNS FROM '+tablename);
except
Exit;
end;
while not Columns.Eof do begin
Proposal.InsertList.Add(Columns.Col('Field'));
Proposal.ItemList.Add(Format(SYNCOMPLETION_PATTERN, [ICONINDEX_FIELD, GetFirstWord(Columns.Col('Type')), Columns.Col('Field')]) );
Columns.Next;
end;
FreeAndNil(Columns);
end;
begin
if not prefCompletionProposal then begin
CanExecute := False;
Exit;
end;
Proposal := Sender as TSynCompletionProposal;
Editor := Proposal.Form.CurrentEditor;
Editor.GetHighlighterAttriAtRowColEx(Editor.CaretXY, Token, TokenTypeInt, Start, Attri);
if TtkTokenKind(TokenTypeInt) = tkString then begin
CanExecute := False;
Exit;
end;
Proposal.InsertList.Clear;
Proposal.ItemList.Clear;
PrevShortToken := Proposal.PreviousToken;
PrevShortToken := WideDequotedStr(PrevShortToken, '`');
rx := TRegExpr.Create;
// Find longer token, ignore EndOfTokenChars, just the last chars up to a whitespace, comma or paranthesis
rx.Expression := '([^\s,\(\)]+)$';
PrevLongToken := Copy(Editor.LineText, 1, Editor.CaretX-2);
if rx.Exec(PrevLongToken) then
PrevLongToken := rx.Match[1]
else
PrevLongToken := '';
// Display list of variables
rx.Expression := '^@@(SESSION|GLOBAL)$';
rx.ModifierI := True;
if rx.Exec(PrevLongToken) then begin
try
Results := Connection.GetResults('SHOW '+UpperCase(rx.Match[1])+' VARIABLES');
while not Results.Eof do begin
Proposal.InsertList.Add(Results.Col(0));
Proposal.ItemList.Add(Format(SYNCOMPLETION_PATTERN, [ICONINDEX_PRIMARYKEY, 'variable', Results.Col(0)+' \color{clSilver}= '+StringReplace(Results.Col(1), '\', '\\', [rfReplaceAll])] ) );
Results.Next;
end;
except
// Just log error in sql log, do not disturb user while typing
end;
Exit;
end;
// Get column-names into the proposal pulldown
// when we write sql like "SELECT t.|col FROM table [AS] t"
// Current limitation: Identifiers (masked or not) containing
// spaces are not detected correctly.
// 1. find the currently edited sql-statement around the cursor position in synmemo
if Editor = SynMemoFilter then begin
// Concat query segments, so the below regular expressions can find structure
sql := 'SELECT * FROM `'+SelectedTable.Name+'` WHERE ' + Editor.Text;
end else begin
// Proposal in one of the query tabs
Queries := parsesql(Editor.Text);
j := 0;
for i:=0 to Queries.Count-1 do begin
Inc(j, Length(Queries[i])+1);
if (j >= Editor.SelStart) or (i = Queries.Count-1) then begin
sql := Queries[i];
break;
end;
end;
FreeAndNil(Queries);
end;
// 2. Parse FROM clause to detect relevant table/view, probably aliased
rx.ModifierG := True;
rx.Expression := '\b(FROM|INTO|UPDATE)\s+(.+)(WHERE|HAVING|ORDER|GROUP)?';
if rx.Exec(sql) then begin
TableClauses := rx.Match[2];
// Ensure tables in JOIN clause(s) are splitted by comma
TableClauses := StringReplace(TableClauses, 'JOIN', ',', [rfReplaceAll, rfIgnoreCase]);
// Split table clauses by commas
Tables := TStringList.Create;
Tables.Delimiter := ',';
Tables.StrictDelimiter := true;
Tables.DelimitedText := TableClauses;
rx.Expression := '(\S+)\s+(AS\s+)?(\S+)';
for i := 0 to Tables.Count - 1 do begin
// If the just typed word equals the alias of this table or the
// tablename itself, set tablename var and break loop
if rx.Exec(Tables[i]) then while true do begin
if PrevShortToken = WideDequotedStr(rx.Match[3],'`') then begin
tablename := rx.Match[1];
break;
end;
if not rx.ExecNext then
break;
end;
if tablename <> '' then
break;
end;
end;
rx.Free;
if (tablename <> '') then begin
// add columns to proposal
addColumns( tablename );
end else if PrevLongToken <> '' then begin
// assuming previoustoken itself is a table
addColumns( PrevLongToken );
end;
if Length(CurrentInput) = 0 then // makes only sense if the user has typed "database."
begin
i := AllDatabases.IndexOf(PrevShortToken);
if i > -1 then begin
// Only display tables from specified db
Screen.Cursor := crHourGlass;
DBObjects := Connection.GetDBObjects(AllDatabases[i]);
for j:=0 to DBObjects.Count-1 do
addTable(DBObjects[j]);
Screen.Cursor := crDefault;
end;
end;
if Proposal.ItemList.count = 0 then begin
// Add databases
for i := 0 to AllDatabases.Count - 1 do begin
Proposal.InsertList.Add(AllDatabases[i]);
Proposal.ItemList.Add(Format(SYNCOMPLETION_PATTERN, [ICONINDEX_DB, 'database', AllDatabases[i]]));
end;
if ActiveDatabase <> '' then begin
// Display tables from current db
DBObjects := Connection.GetDBObjects(ActiveDatabase);
for j:=0 to DBObjects.Count-1 do
addTable(DBObjects[j]);
if Length(CurrentInput) = 0 then // assume that we have already a dbname in memo
Proposal.Position := AllDatabases.Count;
end;
// Add functions
for i := 0 to Length(MySQLFunctions) - 1 do begin
// Don't display unsupported functions here
if MySqlFunctions[i].Version > Connection.ServerVersionInt then
continue;
Proposal.InsertList.Add( MySQLFunctions[i].Name + MySQLFunctions[i].Declaration );
Proposal.ItemList.Add( Format(SYNCOMPLETION_PATTERN, [ICONINDEX_FUNCTION, 'function', MySQLFunctions[i].Name + '\color{clSilver}' + MySQLFunctions[i].Declaration] ) );
end;
// Add keywords
for i := 0 to MySQLKeywords.Count - 1 do begin
Proposal.InsertList.Add( MySQLKeywords[i] );
Proposal.ItemList.Add( Format(SYNCOMPLETION_PATTERN, [ICONINDEX_KEYWORD, 'keyword', MySQLKeywords[i]] ) );
end;
end;
end;
procedure TMainForm.SynMemoQueryStatusChange(Sender: TObject; Changes:
TSynStatusChanges);
begin
ValidateQueryControls(Sender);
UpdateLineCharPanel;
end;
procedure TMainForm.TimerHostUptimeTimer(Sender: TObject);
begin
// Display server uptime
if Assigned(Connection) then
ShowStatusMsg('Uptime: '+FormatTimeNumber(Connection.ServerUptime), 4)
else
ShowStatusMsg('', 4);
end;
procedure TMainForm.ListTablesEditing(Sender: TBaseVirtualTree;
Node: PVirtualNode; Column: TColumnIndex; var Allowed: Boolean);
var
Obj: PDBObject;
begin
// Tables and views can be renamed, routines cannot
Obj := Sender.GetNodeData(Node);
Allowed := Obj.NodeType in [lntTable, lntView];
end;
{***
Rename table after checking the new name for invalid characters
}
procedure TMainForm.ListTablesNewText(Sender: TBaseVirtualTree; Node:
PVirtualNode; Column: TColumnIndex; NewText: String);
var
Obj: PDBObject;
begin
// Fetch data from node
Obj := Sender.GetNodeData(Node);
// Try to rename, on any error abort and don't rename ListItem
try
ensureValidIdentifier( NewText );
// rename table
Connection.Query('RENAME TABLE ' + mask(Obj.Name) + ' TO ' + mask(NewText));
if SynSQLSyn1.TableNames.IndexOf( NewText ) = -1 then begin
SynSQLSyn1.TableNames.Add(NewText);
end;
// Update nodedata
Obj.Name := NewText;
// Now the active tree db has to be updated. But calling RefreshTreeDB here causes an AV
// so we do it manually here
DBTree.InvalidateChildren(FindDBNode(ActiveDatabase), True);
except
on E:Exception do
MessageDlg(E.Message, mtError, [mbOK], 0);
end;
end;
procedure TMainForm.TimerConnectedTimer(Sender: TObject);
begin
if Assigned(Connection) and Connection.Active then begin
// calculate and display connection-time
ShowStatusMsg('Connected: ' + FormatTimeNumber(Connection.ConnectionUptime), 2);
end else begin
ShowStatusMsg('Disconnected.', 2);
end;
end;
procedure TMainForm.Clear2Click(Sender: TObject);
begin
// clear history-memo
Screen.Cursor := crHourglass;
SynMemoSQLLog.Gutter.LineNumberStart := SynMemoSQLLog.Gutter.LineNumberStart + SynMemoSQLLog.Lines.Count;
SynMemoSQLLog.Lines.Clear;
Screen.Cursor := crDefault;
end;
procedure TMainForm.QuickFilterClick(Sender: TObject);
var
Filter, Val, Col: String;
Item : TMenuItem;
begin
// Set filter for "where..."-clause
Item := Sender as TMenuItem;
Col := DataGrid.Header.Columns[DataGrid.FocusedColumn].Text;
Filter := '';
if Item.Tag = 1 then begin
// Item needs prompt
Val := InputBox('Specify filter-value...', Item.Caption, 'Value');
if Val = 'Value' then
Filter := ''
else if Item = QF8 then
Filter := mask(Col) + ' = ''' + Val + ''''
else if Item = QF9 then
Filter := mask(Col) + ' != ''' + Val + ''''
else if Item = QF10 then
Filter := mask(Col) + ' > ''' + Val + ''''
else if Item = QF11 then
Filter := mask(Col) + ' < ''' + Val + ''''
else if Item = QF12 then
Filter := mask(Col) + ' LIKE ''%' + Val + '%''';
end else
Filter := Item.Hint;
if Filter <> '' then begin
SynMemoFilter.UndoList.AddGroupBreak;
SynMemoFilter.SelectAll;
SynmemoFilter.SelText := filter;
ToggleFilterPanel(True);
actApplyFilterExecute(Sender);
end;
end;
procedure TMainForm.popupQueryPopup(Sender: TObject);
begin
// Sets cursor into memo and activates TAction(s) like paste
ActiveQueryMemo.SetFocus;
end;
procedure TMainForm.AutoRefreshSetInterval(Sender: TObject);
var
seconds : String;
secondsInt : Integer;
begin
// set interval for autorefresh-timer
seconds := IntToStr(TimerRefresh.interval div 1000);
if inputquery('Auto refresh','Refresh list every ... second(s):', seconds) then begin
secondsInt := StrToIntDef(seconds, 0);
if secondsInt > 0 then begin
TimerRefresh.Interval := secondsInt * 1000;
TimerRefresh.Enabled := true;
menuAutoRefresh.Checked := true;
end
else
MessageDLG('Seconds must be between 1 and ' + IntToStr(maxint) + '.', mtError, [mbOK], 0);
end;
end;
procedure TMainForm.AutoRefreshToggle(Sender: TObject);
begin
// enable autorefresh-timer
TimerRefresh.Enabled := not TimerRefresh.Enabled;
menuAutoRefresh.Checked := TimerRefresh.Enabled;
end;
procedure TMainForm.SynMemoQueryDragOver(Sender, Source: TObject; X,
Y: Integer; State: TDragState; var Accept: Boolean);
var
src : TControl;
Memo: TSynMemo;
begin
// dragging an object over the query-memo
Memo := ActiveQueryMemo;
src := Source as TControl;
// Accepting drag's from DBTree and QueryHelpers
Accept := (src = DBtree) or (src = ActiveQueryHelpers);
// set x-position of cursor
Memo.CaretX := (x - Memo.Gutter.Width) div Memo.CharWidth - 1 + Memo.LeftChar;
// set y-position of cursor
Memo.CaretY := y div Memo.LineHeight + Memo.TopLine;
if not Memo.Focused then
Memo.SetFocus;
end;
procedure TMainForm.SynMemoQueryDragDrop(Sender, Source: TObject; X,
Y: Integer);
var
src : TControl;
Text, ItemText: String;
LoadText, ShiftPressed: Boolean;
i: Integer;
begin
// dropping a tree node or listbox item into the query-memo
ActiveQueryMemo.UndoList.AddGroupBreak;
src := Source as TControl;
Text := 'Error: Unspecified source control in drag''n drop operation!';
LoadText := True;
ShiftPressed := KeyPressed(VK_SHIFT);
// Check for allowed controls as source has already
// been performed in OnDragOver. So, only do typecasting here.
if src = DBtree then begin
// Insert table or database name. If a table is dropped and Shift is pressed, prepend the db name.
Text := mask(DBtree.Text[DBtree.FocusedNode, 0]);
if (DBtree.GetNodeLevel(DBtree.FocusedNode)=2) and ShiftPressed then
Text := mask(DBtree.Text[DBtree.FocusedNode.Parent, 0]) + '.' + Text;
end else if (src = ActiveQueryHelpers) and (ActiveQueryHelpers.ItemIndex > -1) then begin
// Snippets tab
if ActiveQueryTabset.TabIndex = 3 then begin
QueryLoad( DirnameSnippets + ActiveQueryHelpers.Items[ActiveQueryHelpers.ItemIndex] + '.sql', False );
LoadText := False;
// All other tabs
end else begin
Text := '';
for i := 0 to ActiveQueryHelpers.Items.Count - 1 do begin
if ActiveQueryHelpers.Selected[i] then begin
ItemText := ActiveQueryHelpers.Items[i];
if tabsetQueryHelpers.TabIndex = 0 then
ItemText := mask(ItemText); // Quote column names
if ShiftPressed then
Text := Text + ItemText + ',' + CRLF
else
Text := Text + ItemText + ', ';
end;
end;
Delete(Text, Length(Text)-1, 2);
end;
end;
// Only insert text if no previous action did the job.
// Should be false when dropping a snippet-file here
if LoadText then
ActiveQueryMemo.SelText := Text;
ActiveQueryMemo.UndoList.AddGroupBreak;
// Requires to set focus, as doubleclick actions also call this procedure
ActiveQueryMemo.SetFocus;
end;
procedure TMainForm.SynMemoQueryDropFiles(Sender: TObject; X, Y: Integer;
AFiles: TUnicodeStrings);
var
i: Integer;
begin
// One or more files from explorer or somewhere else was dropped onto the
// query-memo - load their contents into seperate tabs
for i:=0 to AFiles.Count-1 do begin
if i > 0 then
actNewQueryTab.Execute;
QueryLoad(AFiles[i], false);
end;
end;
procedure TMainForm.SynMemoQueryPaintTransient(Sender: TObject; Canvas: TCanvas; TransientType: TTransientType);
var
Editor : TSynEdit;
OpenChars: array of Char;
CloseChars: array of Char;
P: TBufferCoord;
Pix: TPoint;
D: TDisplayCoord;
S: String;
I: Integer;
Attri: TSynHighlighterAttributes;
ArrayLength: Integer;
start: Integer;
TmpCharA, TmpCharB: Char;
function IsCharBracket(AChar: Char): Boolean;
begin
Result := CharInSet(AChar, ['{','[','(','<','}',']',')','>']);
end;
function CharToPixels(P: TBufferCoord): TPoint;
begin
Result := Editor.RowColumnToPixels(Editor.BufferToDisplayPos(P));
end;
begin
// Highlight matching brackets
Editor := TSynEdit(Sender);
if Editor.SelAvail then exit;
ArrayLength := 3;
SetLength(OpenChars, ArrayLength);
SetLength(CloseChars, ArrayLength);
for i := 0 to ArrayLength - 1 do
Case i of
0: begin OpenChars[i] := '('; CloseChars[i] := ')'; end;
1: begin OpenChars[i] := '{'; CloseChars[i] := '}'; end;
2: begin OpenChars[i] := '['; CloseChars[i] := ']'; end;
3: begin OpenChars[i] := '<'; CloseChars[i] := '>'; end;
end;
P := Editor.CaretXY;
D := Editor.DisplayXY;
Start := Editor.SelStart;
if (Start > 0) and (Start <= length(Editor.Text)) then
TmpCharA := Editor.Text[Start]
else
TmpCharA := #0;
if (Start < length(Editor.Text)) then
TmpCharB := Editor.Text[Start + 1]
else
TmpCharB := #0;
if not IsCharBracket(TmpCharA) and not IsCharBracket(TmpCharB) then
Exit;
S := TmpCharB;
if not IsCharBracket(TmpCharB) then begin
P.Char := P.Char - 1;
S := TmpCharA;
end;
Editor.GetHighlighterAttriAtRowCol(P, S, Attri);
if (Editor.Highlighter.SymbolAttribute = Attri) then begin
for i:=Low(OpenChars) to High(OpenChars) do begin
if (S = OpenChars[i]) or (S = CloseChars[i]) then begin
Pix := CharToPixels(P);
Editor.Canvas.Brush.Style := bsSolid;
Editor.Canvas.Font.Assign(Editor.Font);
Editor.Canvas.Font.Style := Attri.Style;
if (TransientType = ttAfter) then begin
Editor.Canvas.Font.Color := clBlack;
Editor.Canvas.Brush.Color := clAqua;
end else begin
Editor.Canvas.Font.Color := Attri.Foreground;
Editor.Canvas.Brush.Color := Attri.Background;
end;
if Editor.Canvas.Font.Color = clNone then
Editor.Canvas.Font.Color := Editor.Font.Color;
if Editor.Canvas.Brush.Color = clNone then
Editor.Canvas.Brush.Color := Editor.Color;
Editor.Canvas.TextOut(Pix.X, Pix.Y, S);
P := Editor.GetMatchingBracketEx(P);
if (P.Char > 0) and (P.Line > 0) then begin
Pix := CharToPixels(P);
if Pix.X > Editor.Gutter.Width then begin
if S = OpenChars[i] then
Editor.Canvas.TextOut(Pix.X, Pix.Y, CloseChars[i])
else Editor.Canvas.TextOut(Pix.X, Pix.Y, OpenChars[i]);
end;
end;
end;
end;
Editor.Canvas.Brush.Style := bsSolid;
end;
end;
procedure TMainForm.popupHostPopup(Sender: TObject);
begin
menuFetchDBitems.Enabled := (PageControlHost.ActivePage = tabDatabases) and (ListDatabases.SelectedCount > 0);
Kill1.Enabled := (PageControlHost.ActivePage = tabProcessList) and (ListProcesses.SelectedCount > 0);
menuEditVariable.Enabled := False;
if Connection.ServerVersionInt >= 40003 then
menuEditVariable.Enabled := (PageControlHost.ActivePage = tabVariables) and Assigned(ListVariables.FocusedNode)
else
menuEditVariable.Hint := STR_NOTSUPPORTED;
end;
procedure TMainForm.Saveastextfile1Click(Sender: TObject);
begin
with TSaveDialog.Create(self) do begin
Filter := 'Textfiles (*.txt)|*.txt|All Files (*.*)|*.*';
DefaultExt := 'txt';
FilterIndex := 1;
Options := [ofOverwritePrompt,ofHideReadOnly,ofEnableSizing];
if Execute then begin
Screen.Cursor := crHourglass;
SynMemoSQLLog.Lines.SaveToFile(Filename);
Screen.Cursor := crdefault;
end;
end;
end;
procedure TMainForm.popupDBPopup(Sender: TObject);
var
L: Cardinal;
HasFocus, InDBTree: Boolean;
Obj: PDBObject;
NodeType: TListNodeType;
begin
// DBtree and ListTables both use popupDB as menu. Find out which of them was rightclicked.
if Sender is TPopupMenu then
InDBTree := (Sender as TPopupMenu).PopupComponent = DBTree
else if Sender is TMenuItem then
InDBTree := TPopupMenu((Sender as TMenuItem).GetParentMenu).PopupComponent = DBTree
else
InDBTree := False;
if InDBtree then begin
HasFocus := Assigned(DBtree.FocusedNode);
if HasFocus then
L := DBtree.GetNodeLevel(DBtree.FocusedNode)
else
L := 0;
NodeType := GetFocusedTreeNodeType;
actCreateDatabase.Enabled := L = 0;
actCreateTable.Enabled := L in [1,2];
actCreateView.Enabled := L in [1,2];
actCreateRoutine.Enabled := L in [1,2];
actCreateTrigger.Enabled := L in [1,2];
actCreateEvent.Enabled := L in [1,2];
actDropObjects.Enabled := L in [1,2];
actCopyTable.Enabled := HasFocus and (NodeType in [lntTable, lntView]);
actEmptyTables.Enabled := HasFocus and (NodeType in [lntTable, lntView]);
actRunRoutines.Enabled := HasFocus and (NodeType in [lntProcedure, lntFunction]);
actEditObject.Enabled := L > 0;
// Show certain items which are valid only here
menuTreeExpandAll.Visible := True;
menuTreeCollapseAll.Visible := True;
menuShowSizeColumn.Visible := True;
actSelectTreeBackground.Visible := True;
end else begin
HasFocus := Assigned(ListTables.FocusedNode);
actCreateDatabase.Enabled := False;
actCreateTable.Enabled := True;
actCreateView.Enabled := True;
actCreateRoutine.Enabled := True;
actCreateTrigger.Enabled := True;
actCreateEvent.Enabled := True;
actDropObjects.Enabled := ListTables.SelectedCount > 0;
actEmptyTables.Enabled := False;
actRunRoutines.Enabled := True;
if HasFocus then begin
Obj := ListTables.GetNodeData(ListTables.FocusedNode);
actEmptyTables.Enabled := Obj.NodeType in [lntTable, lntView];
end;
actEditObject.Enabled := HasFocus;
// Show certain items which are valid only here
actCopyTable.Enabled := actEmptyTables.Enabled;
menuTreeExpandAll.Visible := False;
menuTreeCollapseAll.Visible := False;
menuShowSizeColumn.Visible := False;
actSelectTreeBackground.Visible := False;
end;
actCreateView.Enabled := actCreateView.Enabled and (Connection.ServerVersionInt >= 50001);
actCreateRoutine.Enabled := actCreateRoutine.Enabled and (Connection.ServerVersionInt >= 50003);
actCreateTrigger.Enabled := actCreateTrigger.Enabled and (Connection.ServerVersionInt >= 50002);
actCreateEvent.Enabled := actCreateEvent.Enabled and (Connection.ServerVersionInt >= 50100);
end;
function TMainForm.QueryLoad( filename: String; ReplaceContent: Boolean = true ): Boolean;
var
filecontent: String;
msgtext: String;
LineBreaks: TLineBreaks;
RunFileDialog: TRunSQLFileForm;
begin
Result := False;
if not FileExists(filename) then begin
MessageDlg('File not found: "'+filename+'"', mtError, [mbOK], 0);
Exit;
end;
// Ask for action when loading a big file
if _GetFileSize( filename ) > 5*SIZE_MB then
begin
msgtext := 'The file you are about to load is bigger than '+FormatByteNumber(5*SIZE_MB, 0)+'.' + CRLF + CRLF +
'Do you want to just run the file to avoid loading it completely into the query-editor ( = memory ) ?' + CRLF + CRLF +
'Press' + CRLF +
' [Yes] to run the file without loading it into the editor' + CRLF +
' [No] to load the file into the query editor' + CRLF +
' [Cancel] to cancel file opening.';
case MessageDlg( msgtext, mtWarning, [mbYes, mbNo, mbCancel], 0 ) of
// Run the file, don't load it into the editor
mrYes:
begin
RunFileDialog := TRunSQLFileForm.Create(Self);
RunFileDialog.SQLFileName := filename;
RunFileDialog.ShowModal;
RunFileDialog.Free;
// Add filename to history menu
if Pos( DirnameSnippets, filename ) = 0 then
AddOrRemoveFromQueryLoadHistory( filename, true );
// Don't load into editor
Abort;
end;
// Do nothing here, go ahead and load the file normally into the editor
mrNo:;
// Cancel opening file
mrCancel: Abort;
end;
end;
// Load file and add that to the undo-history of SynEdit.
// Normally we would do a simple SynMemo.Lines.LoadFromFile but
// this would prevent SynEdit from adding this step to the undo-history
// so we have to do it by replacing the SelText property
Screen.Cursor := crHourGlass;
if not QueryTabActive then
PagecontrolMain.ActivePage := tabQuery;
LogSQL('Loading file "'+filename+'" into query tab #'+IntToStr(ActiveQueryTab.Number)+' ...', lcInfo);
try
filecontent := ReadTextfile(filename);
if Pos( DirnameSnippets, filename ) = 0 then
AddOrRemoveFromQueryLoadHistory( filename, true );
FillPopupQueryLoad;
ActiveQueryMemo.UndoList.AddGroupBreak;
if ScanNulChar(filecontent) then begin
filecontent := RemoveNulChars(filecontent);
MessageDlg(SContainsNulCharFile, mtInformation, [mbOK], 0);
end;
ActiveQueryMemo.BeginUpdate;
LineBreaks := ScanLineBreaks(filecontent);
if ReplaceContent then begin
ActiveQueryMemo.SelectAll;
ActiveQueryTab.MemoLineBreaks := LineBreaks;
end else begin
if (ActiveQueryTab.MemoLineBreaks <> lbsNone) and (ActiveQueryTab.MemoLineBreaks <> LineBreaks) then
ActiveQueryTab.MemoLineBreaks := lbsMixed
else
ActiveQueryTab.MemoLineBreaks := LineBreaks;
end;
if ActiveQueryTab.MemoLineBreaks = lbsMixed then
MessageDlg('This file contains mixed linebreaks. They have been converted to Windows linebreaks (CR+LF).', mtInformation, [mbOK], 0);
ActiveQueryMemo.SelText := filecontent;
ActiveQueryMemo.SelStart := ActiveQueryMemo.SelEnd;
ActiveQueryMemo.EndUpdate;
SetTabCaption(PageControlMain.ActivePageIndex, sstr(ExtractFilename(filename), 70));
ActiveQueryMemo.Modified := False;
ActiveQueryTab.MemoFilename := filename;
Result := True;
except on E:Exception do
// File does not exist, is locked or broken
MessageDlg(E.message, mtError, [mbOK], 0);
end;
Screen.Cursor := crDefault;
end;
procedure TMainForm.SaveDialogExportDataTypeChange(Sender: TObject);
begin
// Set default file-extension of saved file and options on the dialog to show
with SaveDialogExportData do begin
Case FilterIndex of
1: DefaultExt := 'csv';
2: DefaultExt := 'html';
3: DefaultExt := 'xml';
4: DefaultExt := 'sql';
end;
end;
end;
procedure TMainForm.popupDataGridPopup(Sender: TObject);
var
y,m,d,h,i,s,ms : Word;
cpText, Col, value : String;
CellFocused: Boolean;
const
CLPBRD : String = 'CLIPBOARD';
begin
CellFocused := Assigned(DataGrid.FocusedNode) and (DataGrid.FocusedColumn > NoColumn);
DataInsertDateTime.Enabled := CellFocused;
QFvalues.Enabled := CellFocused;
if not CellFocused then
Exit;
decodedate(now, y, m, d);
decodetime(now, h, i, s, ms);
DataDateTime.Caption := Format('%.4d-%.2d-%.2d %.2d:%.2d:%.2d', [y,m,d,h,i,s]);
DataDate.Caption := Format('%.4d-%.2d-%.2d', [y,m,d]);
DataTime.Caption := Format('%.2d:%.2d:%.2d', [h,i,s]);
DataTimestamp.caption := Format('%.4d%.2d%.2d%.2d%.2d%.2d', [y,m,d,h,i,s]);
DataYear.Caption := Format('%.4d', [y]);
// Manipulate the Quick-filter menuitems
DataGridEnsureFullRow(DataGrid, DataGrid.FocusedNode);
Col := mask(DataGrid.Header.Columns[DataGrid.FocusedColumn].Text);
// 1. block: include selected columnname and value from datagrid in caption
if DataGridResult.Rows[DataGrid.FocusedNode.Index].Cells[DataGrid.FocusedColumn].IsNull then begin
QF1.Hint := Col + ' IS NULL';
QF2.Hint := Col + ' IS NOT NULL';
QF3.Visible := False;
QF4.Visible := False;
QF5.Visible := False;
QF6.Visible := False;
QF7.Visible := False;
end else begin
value := DataGrid.Text[DataGrid.FocusedNode, DataGrid.FocusedColumn];
QF1.Hint := Col + ' = ' + esc( value );
QF2.Hint := Col + ' != ' + esc( value );
QF3.Hint := Col + ' > ' + esc( value );
QF4.Hint := Col + ' < ' + esc( value );
QF5.Hint := Col + ' LIKE ''' + esc( value, true ) + '%''';
QF6.Hint := Col + ' LIKE ''%' + esc( value, true ) + '''';
QF7.Hint := Col + ' LIKE ''%' + esc( value, true ) + '%''';
QF3.Visible := True;
QF4.Visible := True;
QF5.Visible := True;
QF6.Visible := True;
QF7.Visible := True;
end;
// 2. block: include only selected columnname in caption
QF8.Hint := Col + ' = "..."';
QF9.Hint := Col + ' != "..."';
QF10.Hint := Col + ' > "..."';
QF11.Hint := Col + ' < "..."';
QF12.Hint := Col + ' LIKE "%...%"';
QF13.Hint := Col + ' IS NULL';
QF14.Hint := Col + ' IS NOT NULL';
// 3. block: include selected columnname and clipboard-content in caption for one-click-filtering
cpText := Clipboard.AsText;
if Length(cpText) < SIZE_KB then begin
QF15.Enabled := true; QF15.Hint := Col + ' = ' + esc( cpText );
QF16.Enabled := true; QF16.Hint := Col + ' != ' + esc( cpText );
QF17.Enabled := true; QF17.Hint := Col + ' > ' + esc( cpText );
QF18.Enabled := true; QF18.Hint := Col + ' < ' + esc( cpText );
QF19.Enabled := true; QF19.Hint := Col + ' LIKE ''%' + esc( cpText, true ) + '%''';
end else begin
QF15.Enabled := false; QF15.Hint := Col + ' = ' + CLPBRD;
QF16.Enabled := false; QF16.Hint := Col + ' != ' + CLPBRD;
QF17.Enabled := false; QF17.Hint := Col + ' > ' + CLPBRD;
QF18.Enabled := false; QF18.Hint := Col + ' < ' + CLPBRD;
QF19.Enabled := false; QF19.Hint := Col + ' LIKE %' + CLPBRD + '%';
end;
for i:=0 to menuQuickFilter.Count-1 do begin
if (menuQuickFilter[i].Caption <> '-') // Not a separator
and (menuQuickFilter[i].Count = 0) // Not a menu with subitems
and (menuQuickFilter[i].Action = nil) // Not some special item
then
menuQuickFilter[i].Caption := sstr(menuQuickFilter[i].Hint, 100);
end;
end;
procedure TMainForm.QFvaluesClick(Sender: TObject);
var
Data: TMySQLQuery;
Col: String;
Item: TMenuItem;
i: Integer;
begin
// Create a list of distinct column values in selected table
for i:=QFvalues.Count-1 downto 1 do
QFvalues.Delete(i);
QFvalues[0].Caption := '';
QFvalues[0].Hint := '';
QFvalues[0].OnClick := nil;
if DataGrid.FocusedColumn = NoColumn then
Exit;
Col := DataGridResult.Columns[DataGrid.FocusedColumn].Name;
ShowStatusMsg('Fetching distinct values ...');
Data := Connection.GetResults('SELECT '+mask(Col)+', COUNT(*) AS c FROM '+mask(SelectedTable.Name)+
' GROUP BY '+mask(Col)+' ORDER BY c DESC, '+mask(Col)+' LIMIT 30');
for i:=0 to Data.RecordCount-1 do begin
if QFvalues.Count > i then
Item := QFvalues[i]
else begin
Item := TMenuItem.Create(QFvalues);
QFvalues.Add(Item);
end;
Item.Hint := mask(Col)+'='+esc(Data.Col(Col));
Item.Caption := sstr(Item.Hint, 100) + ' (' + FormatNumber(Data.Col('c')) + ')';
Item.OnClick := QuickFilterClick;
Data.Next;
end;
ShowStatusMsg(STATUS_MSG_READY);
end;
procedure TMainForm.InsertDate(Sender: TObject);
var d : String;
begin
// Insert date/time-value into table
d := (sender as TMenuItem).Caption;
delete(d, Pos('&', d), 1);
DataGrid.Text[DataGrid.FocusedNode, DataGrid.FocusedColumn] := d;
end;
function TMainForm.GetActiveDatabase: String;
var
s: PVirtualNode;
begin
// Find currently selected database node in database tree,
// or the parent if a table is currently selected.
if csDestroying in ComponentState then
Exit;
s := DBtree.FocusedNode;
if not Assigned(s) then Result := ''
else case DBtree.GetNodeLevel(s) of
2: Result := AllDatabases[s.Parent.Index];
1: Result := AllDatabases[s.Index];
else Result := '';
end;
end;
function TMainForm.GetSelectedTable: TDBObject;
var
Node: PVirtualNode;
DBObjects: TDBObjectList;
begin
Node := DBtree.FocusedNode;
Result := TDBObject.Create;
if Assigned(Node) and (DBtree.GetNodeLevel(Node)=2) then begin
DBObjects := Connection.GetDBObjects(ActiveDatabase);
if Node.Index < DBObjects.Count then
Result.Assign(DBObjects[Node.Index]);
end;
end;
function TMainForm.GetTreeNodeType(Tree: TBaseVirtualTree; Node: PVirtualNode): TListNodeType;
var
DBObjects: TDBObjectList;
begin
Result := lntNone;
if Assigned(Node) then case Tree.GetNodeLevel(Node) of
1: Result := lntDb;
2: begin
DBObjects := Connection.GetDBObjects((Tree as TVirtualStringTree).Text[Node.Parent, 0]);
Result := DBObjects[Node.Index].NodeType;
end;
end;
end;
function TMainForm.GetFocusedTreeNodeType: TListNodeType;
begin
Result := GetTreeNodeType(DBTree, DBtree.FocusedNode);
end;
procedure TMainForm.SelectDBObject(Text: String; NodeType: TListNodeType);
var
dbnode, tnode, snode: PVirtualNode;
begin
debug('SelectDBObject()');
// Detect db node
case DBtree.GetNodeLevel(DBtree.FocusedNode) of
1: dbnode := DBtree.FocusedNode;
2: dbnode := DBtree.FocusedNode.Parent;
else raise Exception.Create('No selection in tree, could not determine active db.');
end;
snode := nil;
// 1st search, case sensitive for lower-case-tablenames=0 servers
tnode := DBtree.GetFirstChild(dbnode);
while Assigned(tnode) do begin
// Select table node if it has the wanted caption
if (DBtree.Text[tnode, 0] = Text) and (GetTreeNodeType(DBTree, tnode) = NodeType) then begin
snode := tnode;
break;
end;
tnode := DBtree.GetNextSibling(tnode);
end;
// 2nd search, case insensitive now
if not Assigned(snode) then begin
tnode := DBtree.GetFirstChild(dbnode);
while Assigned(tnode) do begin
// Select table node if it has the wanted caption
if (AnsiCompareText(DBtree.Text[tnode, 0], Text) = 0) and (GetTreeNodeType(DBtree, tnode) = NodeType) then begin
snode := tnode;
break;
end;
tnode := DBtree.GetNextSibling(tnode);
end;
end;
if Assigned(snode) then begin
// Ensure table node will be visible
DBTree.ScrollIntoView(snode, False);
DBtree.Expanded[dbnode] := True;
DBtree.Selected[snode] := True;
// Implicitely calls OnFocusChanged:
DBTree.FocusedNode := nil;
DBTree.FocusedNode := snode;
exit;
end;
raise Exception.Create('Table node ' + Text + ' not found in tree.');
end;
procedure TMainForm.SetSelectedDatabase(db: String);
var
n, f: PVirtualNode;
begin
if db = '' then
n := DBtree.GetFirst
else
n := FindDBNode(db);
if Assigned(n) then begin
// Set focus to db node, if current focus is outside
f := DBtree.FocusedNode;
if (not Assigned(f)) or (f.Parent <> n) then
SelectNode(DBtree, n);
end else
raise Exception.Create('Database node ' + db + ' not found in tree.');
end;
{**
Column selection for datagrid
}
procedure TMainForm.btnDataClick(Sender: TObject);
var
btn : TToolButton;
frm : TForm;
begin
btn := (Sender as TToolButton);
if (btn = tbtnDataColumns) or (btn = tbtnDataSorting) then begin
// Create desired form for SELECT and ORDER buttons
btn.Down := not btn.Down;
if not btn.Down then Exit;
if btn = tbtnDataColumns then
frm := TColumnSelectionForm.Create(self)
else if btn = tbtnDataSorting then
frm := TDataSortingForm.Create(self)
else
frm := TForm.Create(self); // Dummy fallback, should never get created
// Position new form relative to btn's position
frm.Top := btn.ClientOrigin.Y + btn.Height;
frm.Left := btn.ClientOrigin.X + btn.Width - frm.Width;
// Display form
frm.Show;
end else if btn = tbtnDataFilter then begin
// Unhide inline filter panel
ToggleFilterPanel;
FilterPanelManuallyOpened := pnlFilter.Visible;
if FilterPanelManuallyOpened then
SynMemoFilter.SetFocus;
end;
end;
{**
Tabset right to query-memo was clicked
}
procedure TMainForm.tabsetQueryHelpersChange(Sender: TObject; NewTab: Integer;
var AllowChange: Boolean);
var
i, idx : Integer;
SnippetsAccessible : Boolean;
Files: TStringList;
Col: TTableColumn;
begin
ActiveQueryHelpers.Items.BeginUpdate;
ActiveQueryHelpers.Items.Clear;
// By default sorted alpabetically
ActiveQueryHelpers.Sorted := True;
// By default disable all items in popupmenu, enable them when needed
menuQueryHelpersGenerateInsert.Enabled := False;
menuQueryHelpersGenerateUpdate.Enabled := False;
menuQueryHelpersGenerateDelete.Enabled := False;
menuInsertSnippetAtCursor.Enabled := False;
menuLoadSnippet.Enabled := False;
menuDeleteSnippet.Enabled := False;
menuExplore.Enabled := False;
menuHelp.Enabled := False;
ActiveQueryHelpers.MultiSelect := True;
case NewTab of
0: // Cols
begin
// Keep native order of columns
ActiveQueryHelpers.Sorted := False;
case SelectedTable.NodeType of
lntTable, lntView: begin
menuQueryHelpersGenerateInsert.Enabled := True;
menuQueryHelpersGenerateUpdate.Enabled := True;
menuQueryHelpersGenerateDelete.Enabled := True;
for i:=0 to SelectedTableColumns.Count-1 do begin
Col := TTableColumn(SelectedTableColumns[i]);
ActiveQueryHelpers.Items.Add(Col.Name);
end;
end;
lntFunction, lntProcedure: if Assigned(ActiveObjectEditor) then begin
for i:=0 to TfrmRoutineEditor(ActiveObjectEditor).Parameters.Count-1 do
ActiveQueryHelpers.Items.Add(TfrmRoutineEditor(ActiveObjectEditor).Parameters[i].Name);
end;
end;
end;
1: // SQL functions
begin
// State of items in popupmenu
menuHelp.Enabled := True;
for i := 0 to Length(MySQLFunctions) - 1 do
begin
// Don't display unsupported functions here
if MySqlFunctions[i].Version > Connection.ServerVersionInt then
continue;
ActiveQueryHelpers.Items.Add( MySQLFunctions[i].Name + MySQLFunctions[i].Declaration );
end;
end;
2: // SQL keywords
begin
// State of items in popupmenu
menuHelp.Enabled := True;
for i := 0 to MySQLKeywords.Count - 1 do
ActiveQueryHelpers.Items.Add(MySQLKeywords[i]);
end;
3: // SQL Snippets
begin
ActiveQueryHelpers.MultiSelect := False;
Files := getFilesFromDir( DirnameSnippets, '*.sql', true );
for i := 0 to Files.Count - 1 do
ActiveQueryHelpers.Items.Add(Files[i]);
Files.Free;
// State of items in popupmenu
SnippetsAccessible := ActiveQueryHelpers.Items.Count > 0;
menuDeleteSnippet.Enabled := SnippetsAccessible;
menuInsertSnippetAtCursor.Enabled := SnippetsAccessible;
menuLoadSnippet.Enabled := SnippetsAccessible;
menuExplore.Enabled := True;
end;
end;
// Restore last selected item in tab
for i := 0 to Length(ActiveQueryTab.HelperListSelectedItems[NewTab]) - 1 do begin
idx := ActiveQueryTab.HelperListSelectedItems[NewTab][i];
if idx < ActiveQueryHelpers.Count then
ActiveQueryHelpers.Selected[idx] := True;
end;
ActiveQueryHelpers.Items.EndUpdate;
end;
{**
Insert string from listbox with query helpers into SQL
memo at doubleclick
}
procedure TMainForm.lboxQueryHelpersDblClick(Sender: TObject);
var
m: TSynMemo;
begin
m := ActiveQueryMemo;
m.DragDrop(Sender, m.CaretX, m.CaretY);
end;
{**
Remember last used items in query helper tabs
}
procedure TMainForm.lboxQueryHelpersClick(Sender: TObject);
var
i, s, idx: Integer;
begin
s := ActiveQueryTabset.TabIndex;
SetLength(ActiveQueryTab.HelperListSelectedItems[s], 0);
for i := 0 to ActiveQueryHelpers.Count - 1 do if ActiveQueryHelpers.Selected[i] then begin
idx := Length(ActiveQueryTab.HelperListSelectedItems[s]);
SetLength(ActiveQueryTab.HelperListSelectedItems[s], idx+1);
ActiveQueryTab.HelperListSelectedItems[s][idx] := i;
end;
end;
{**
Insert function name from popupmenu to query memo
}
procedure TMainForm.insertFunction(Sender: TObject);
var
f : String;
sm : TSynMemo;
begin
// Detect which memo is focused
if SynMemoFilter.Focused then
sm := SynMemoFilter
else
sm := ActiveQueryMemo;
// Restore function name from array
f := MySQLFunctions[TControl(Sender).tag].Name
+ MySQLFunctions[TControl(Sender).tag].Declaration;
sm.UndoList.AddGroupBreak;
sm.SelText := f;
sm.UndoList.AddGroupBreak;
if not SynMemoFilter.Focused then
ValidateQueryControls(Sender);
end;
{**
Delete a snippet file
}
procedure TMainForm.menuDeleteSnippetClick(Sender: TObject);
var
snippetfile : String;
mayChange : Boolean;
begin
// Don't do anything if no item was selected
if ActiveQueryHelpers.ItemIndex = -1 then
abort;
snippetfile := DirnameSnippets + ActiveQueryHelpers.Items[ ActiveQueryHelpers.ItemIndex ] + '.sql';
if MessageDlg( 'Delete snippet file? ' + CRLF + snippetfile, mtConfirmation, [mbOk, mbCancel], 0) = mrOk then
begin
Screen.Cursor := crHourGlass;
if DeleteFile( snippetfile ) then
begin
// Refresh list with snippets
mayChange := True; // Unused; satisfies callee parameter collection which is probably dictated by tabset.
tabsetQueryHelpersChange( Sender, ActiveQueryTabset.TabIndex, mayChange );
FillPopupQueryLoad;
end
else
begin
Screen.Cursor := crDefault;
MessageDlg( 'Failed deleting ' + snippetfile, mtError, [mbOK], 0 );
end;
Screen.Cursor := crDefault;
end;
end;
{**
Load snippet at cursor
}
procedure TMainForm.menuInsertSnippetAtCursorClick(Sender: TObject);
begin
QueryLoad( DirnameSnippets + ActiveQueryHelpers.Items[ActiveQueryHelpers.ItemIndex] + '.sql', False );
end;
{**
Load snippet and replace content
}
procedure TMainForm.menuLoadSnippetClick(Sender: TObject);
begin
QueryLoad( DirnameSnippets + ActiveQueryHelpers.Items[ActiveQueryHelpers.ItemIndex] + '.sql', True );
end;
{**
Open snippets-directory in Explorer
}
procedure TMainForm.menuExploreClick(Sender: TObject);
begin
// Normally the snippets folder is created at installation. But it sure
// can be the case that it has been deleted or that the application was
// not installed properly. Ask if we should create the folder now.
if DirectoryExists( DirnameSnippets ) then
ShellExec( '', DirnameSnippets )
else
if MessageDlg( 'Snippets folder does not exist: ' + DirnameSnippets + CRLF + CRLF + 'This folder is normally created when you install '+appname+'.' + CRLF + CRLF + 'Shall it be created now?',
mtWarning, [mbYes, mbNo], 0 ) = mrYes then
try
Screen.Cursor := crHourglass;
ForceDirectories( DirnameSnippets );
finally
Screen.Cursor := crDefault;
end;
end;
procedure TMainForm.menuFetchDBitemsClick(Sender: TObject);
var
Node: PVirtualNode;
db: String;
begin
// Fill db object cache of selected databases
try
Screen.Cursor := crHourglass;
Node := ListDatabases.GetFirstSelected;
while Assigned(Node) do begin
db := ListDatabases.Text[Node, 0];
Connection.GetDBObjects(db, True);
ListDatabases.RepaintNode(Node);
DBtree.RepaintNode(FindDBNode(db));
Node := ListDatabases.GetNextSelected(Node);
end;
finally
Screen.Cursor := crDefault;
end;
end;
{**
Tell a VirtualStringTree the mem size to allocate per node
}
procedure TMainForm.vstGetNodeDataSize(Sender: TBaseVirtualTree; var
NodeDataSize: Integer);
begin
NodeDataSize := SizeOf(TVTreeData);
end;
{**
Various lists initialize their nodes by calling the following procedure
once per node
}
procedure TMainForm.vstInitNode(Sender: TBaseVirtualTree; ParentNode,
Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
var
NodeData : PVTreeData;
a : TVTreeDataArray;
begin
// Get the pointer to the node data
NodeData := Sender.GetNodeData(Node);
// Fetch data array
a := GetVTreeDataArray( Sender )^;
// Bind data to node
NodeData.Captions := a[Node.Index].Captions;
NodeData.ImageIndex := a[Node.Index].ImageIndex;
NodeData.NodeType := a[Node.Index].NodeType;
end;
{**
Free data of a node
}
procedure TMainForm.vstFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
var
b : PVTreeDataArray;
begin
// Detect which global array should be processed
b := GetVTreeDataArray( Sender );
// TODO: If you optimize 'b' out of the code, the compiler will
// sometimes generate code that causes a new array here instead of
// a reference to the global array, thus breaking SetLength. Find
// out why...
//TestVTreeDataArray(b);
if (Low(b^) < 0) or (High(b^) < 0) then raise Exception.Create('Internal error: unsupported array bounds.');
if Node.Index + 1 < Cardinal(High(b^)) then
begin
// Delete node somewhere in the middle of the array
// Taken from http://delphi.about.com/cs/adptips2004/a/bltip0204_2.htm
System.Move(
b^[Node.Index + 1],
b^[Node.Index],
(Cardinal(Length(b^)) - (Node.Index - Cardinal(Low(b^))) - 1) * SizeOf(TVTreeData)
);
end;
SetLength(b^, Length(b^) - 1);
end;
{**
A node in a VirtualStringTree gets visible and asks which text it shall display
}
procedure TMainForm.vstGetText(Sender: TBaseVirtualTree; Node:
PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var CellText: String);
var
NodeData : PVTreeData;
i : Integer;
begin
// Get pointer to node which gets displayed
NodeData := Sender.GetNodeData(Node);
// Column is -1 if no column headers are defined
if Column = -1 then
i := 0
else
i := Column;
// Avoid AV, don't exceed Captions content
if NodeData.Captions.Count > i then
CellText := NodeData.Captions[i]
else
CellText := '';
end;
{**
A node in a VirtualStringTree gets visible and asks which icon it shall display
}
procedure TMainForm.vstGetImageIndex(Sender: TBaseVirtualTree; Node:
PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex; var Ghosted:
Boolean; var ImageIndex: Integer);
var
NodeData : PVTreeData;
begin
// Display icon only for leftmost cell (0) or for tree nodes (-1)
if Column > 0 then
exit;
// Prevent state images, overlaying the normal image
if not (Kind in [ikNormal, ikSelected]) then Exit;
// Get pointer to node which gets displayed
NodeData := Sender.GetNodeData(Node);
ImageIndex := NodeData.ImageIndex;
end;
{**
A column header of a VirtualStringTree was clicked:
Toggle the sort direction
}
procedure TMainForm.vstHeaderClick(Sender: TVTHeader; HitInfo: TVTHeaderHitInfo);
begin
// Don't call sorting procedure on right click
// Some list-headers have a contextmenu which should popup then.
if HitInfo.Button = mbRight then
Exit;
// Beginning with VT's r181, this proc is also called when doubleclicking-to-autofit
// Seems buggy in VT as this suddenly calls it with Column=-1 in those cases.
// See also issue #1150
if HitInfo.Column = NoColumn then
Exit;
if Sender.SortColumn <> HitInfo.Column then
Sender.SortColumn := HitInfo.Column
else if Sender.SortDirection = sdAscending then
Sender.SortDirection := sdDescending
else
Sender.SortDirection := sdAscending;
Sender.Treeview.SortTree( HitInfo.Column, Sender.SortDirection );
end;
{**
Sorting a column of a VirtualTree by comparing two cells
}
procedure TMainForm.vstCompareNodes(Sender: TBaseVirtualTree; Node1,
Node2: PVirtualNode; Column: TColumnIndex; var Result: Integer);
var
VT: TVirtualStringTree;
CellText1, CellText2 : String;
Number1, Number2 : Extended;
begin
VT := Sender as TVirtualStringTree;
CellText1 := VT.Text[Node1, Column];
CellText2 := VT.Text[Node2, Column];
// Map value "0" to "N/A" strings
if CellText1 = '' then
CellText1 := '0';
if CellText2 = '' then
CellText2 := '0';
// Apply different comparisons for numbers and text
if StrToIntDef( copy(CellText1,0,1), -1 ) <> -1 then
begin
// Assuming numeric values
Number1 := MakeFloat( CellText1 );
Number2 := MakeFloat( CellText2 );
if Number1 > Number2 then
Result := 1
else if Number1 = Number2 then
Result := 0
else if Number1 < Number2 then
Result := -1;
end
else begin
// Compare Strings
Result := CompareText( CellText1, CellText2 );
end;
end;
{**
VirtualTree was painted. Adjust background color of sorted column.
}
procedure TMainForm.vstAfterPaint(Sender: TBaseVirtualTree;
TargetCanvas: TCanvas);
var
i: Integer;
h: TVTHeader;
NewColor: TColor;
begin
h := (Sender as TVirtualStringTree).Header;
for i:=0 to h.Columns.Count-1 do begin
NewColor := clWindow;
if h.SortColumn = i then
NewColor := ColorAdjustBrightness(NewColor, COLORSHIFT_SORTCOLUMNS);
h.Columns[i].Color := NewColor;
end;
end;
{**
Return the data array which belongs to a VirtualTree component
}
function TMainForm.GetVTreeDataArray( VT: TBaseVirtualTree ): PVTreeDataArray;
begin
if VT = ListVariables then
Result := @VTRowDataListVariables
else if VT = ListStatus then
Result := @VTRowDataListStatus
else if VT = ListCommandStats then
Result := @VTRowDataListCommandStats
else if VT = ListProcesses then
Result := @VTRowDataListProcesses
else begin
raise Exception.Create( VT.ClassName + ' "' + VT.Name + '" doesn''t have an assigned array with data.' );
end;
end;
{**
Internal: Test quality of code/compiler.
}
procedure TMainForm.TestVTreeDataArray( P: PVTreeDataArray );
begin
if P = @VTRowDataListVariables then Exit;
if P = @VTRowDataListStatus then Exit;
if P = @VTRowDataListCommandStats then Exit;
if P = @VTRowDataListProcesses then Exit;
raise Exception.Create('Assertion failed: Invalid global VT array.');
end;
{**
Save setup of a VirtualStringTree to registry
}
procedure TMainForm.SaveListSetup( List: TVirtualStringTree );
var
i : Byte;
ColWidths, ColsVisible, ColPos, Regname: String;
OwnerForm: TWinControl;
begin
ColWidths := '';
ColsVisible := '';
ColPos := '';
for i := 0 to List.Header.Columns.Count - 1 do
begin
// Column widths
if ColWidths <> '' then
ColWidths := ColWidths + ',';
ColWidths := ColWidths + IntToStr(List.Header.Columns[i].Width);
// Column visibility
if coVisible in List.Header.Columns[i].Options then
begin
if ColsVisible <> '' then
ColsVisible := ColsVisible + ',';
ColsVisible := ColsVisible + IntToStr(i);
end;
// Column position
if ColPos <> '' then
ColPos := ColPos + ',';
ColPos := ColPos + IntToStr(List.Header.Columns[i].Position);
end;
// Lists can have the same name over different forms or frames. Find parent form or frame,
// so we can prepend its name into the registry value name.
OwnerForm := GetParentFormOrFrame(List);
// On a windows shutdown, GetParentForm() seems sporadically unable to find the owner form
// In that case we would cause an exception when accessing it. Emergency break in that case.
// See issue #1462
// TODO: Test this, probably fixed by implementing GetParentFormOrFrame, and then again, probably not.
if not Assigned(OwnerForm) then
Exit;
Regname := OwnerForm.Name + '.' + List.Name;
OpenRegistry;
MainReg.WriteString( REGPREFIX_COLWIDTHS + Regname, ColWidths );
MainReg.WriteString( REGPREFIX_COLSVISIBLE + Regname, ColsVisible );
MainReg.WriteString( REGPREFIX_COLPOS + Regname, ColPos );
end;
{**
Restore setup of VirtualStringTree from registry
}
procedure TMainForm.RestoreListSetup( List: TVirtualStringTree );
var
i : Byte;
colwidth, colpos : Integer;
Value : String;
ValueList : TStringList;
Regname: String;
OwnerForm: TWinControl;
begin
ValueList := TStringList.Create;
// Column widths
OwnerForm := GetParentFormOrFrame(List);
Regname := OwnerForm.Name + '.' + List.Name;
Value := GetRegValue(REGPREFIX_COLWIDTHS + Regname, '');
if Value <> '' then begin
ValueList := Explode( ',', Value );
for i := 0 to ValueList.Count - 1 do
begin
colwidth := MakeInt(ValueList[i]);
// Check if column number exists and width is at least 1 pixel
if (List.Header.Columns.Count > i) and (colwidth > 0) then
List.Header.Columns[i].Width := colwidth;
end;
end;
// Column visibility
Value := GetRegValue(REGPREFIX_COLSVISIBLE + Regname, '');
if Value <> '' then begin
ValueList := Explode( ',', Value );
for i:=0 to List.Header.Columns.Count-1 do begin
if ValueList.IndexOf( IntToStr(i) ) > -1 then
List.Header.Columns[i].Options := List.Header.Columns[i].Options + [coVisible]
else
List.Header.Columns[i].Options := List.Header.Columns[i].Options - [coVisible];
end;
end;
// Column position
Value := GetRegValue(REGPREFIX_COLPOS + Regname, '');
if Value <> '' then begin
ValueList := Explode( ',', Value );
for i := 0 to ValueList.Count - 1 do
begin
colpos := MakeInt(ValueList[i]);
// Check if column number exists
if List.Header.Columns.Count > i then
List.Header.Columns[i].Position := colpos;
end;
end;
ValueList.Free;
end;
{**
Start writing logfile.
Called either in FormShow or after closing preferences dialog
}
procedure TMainForm.ActivateFileLogging;
var
LogfilePattern : String;
i : Integer;
begin
// Ensure directory exists
if prefDirnameSessionLogs[Length(prefDirnameSessionLogs)] <> '\' then
prefDirnameSessionLogs := prefDirnameSessionLogs + '\';
ForceDirectories(prefDirnameSessionLogs);
// Determine free filename
LogfilePattern := '%s %.6u.log';
i := 1;
FileNameSessionLog := prefDirnameSessionLogs + goodfilename(Format(LogfilePattern, [SessionName, i]));
while FileExists( FileNameSessionLog ) do
begin
inc(i);
FileNameSessionLog := prefDirnameSessionLogs + goodfilename(Format(LogfilePattern, [SessionName, i]));
end;
// Create file handle for writing
AssignFile( FileHandleSessionLog, FileNameSessionLog );
{$I-} // Supress errors
if FileExists(FileNameSessionLog) then
Append(FileHandleSessionLog)
else
Rewrite(FileHandleSessionLog);
{$I+}
if IOResult <> 0 then
begin
MessageDlg('Error opening session log file:'+CRLF+FileNameSessionLog+CRLF+CRLF+'Logging is disabled now.', mtError, [mbOK], 0);
prefLogToFile := False;
end else
prefLogToFile := True;
// Update popupMenu items
menuLogToFile.Checked := prefLogToFile;
menuOpenLogFolder.Enabled := prefLogToFile;
end;
{**
Close logfile.
Called in FormClose, in ActivateFileLogging and on closing preferences dialog
}
procedure TMainForm.DeactivateFileLogging;
begin
prefLogToFile := False;
{$I-} // Supress errors
CloseFile(FileHandleSessionLog);
{$I+}
// Reset IOResult so later checks in ActivateFileLogging doesn't get an old value
IOResult;
// Update popupMenu items
menuLogToFile.Checked := prefLogToFile;
menuOpenLogFolder.Enabled := prefLogToFile;
end;
{**
Display tooltips in VirtualTrees. Imitates default behaviour of TListView.
}
procedure TMainForm.vstGetHint(Sender: TBaseVirtualTree; Node:
PVirtualNode; Column: TColumnIndex; var LineBreakStyle:
TVTTooltipLineBreakStyle; var HintText: String);
var
r : TRect;
DisplayedWidth,
NeededWidth : Integer;
Tree: TVirtualStringTree;
begin
Tree := TVirtualStringTree(Sender);
HintText := Tree.Text[Node, Column];
HintText := sstr(HintText, SIZE_KB);
LineBreakStyle := hlbForceMultiLine;
// Check if the list has shortened the text
r := Tree.GetDisplayRect(Node, Column, True);
DisplayedWidth := r.Right-r.Left;
NeededWidth := Canvas.TextWidth(HintText) + Tree.TextMargin*2;
//debug(format('need: %d, given: %d, font: %s %d', [NeededWidth, DisplayedWidth, canvas.Font.Name, canvas.Font.Size]));
// Disable displaying hint if text is displayed completely in list
if NeededWidth <= DisplayedWidth then
HintText := '';
end;
{**
Enable/disable file logging by popupmenuclick
}
procedure TMainForm.menuLogToFileClick(Sender: TObject);
var
OldprefLogToFile: Boolean;
begin
OldprefLogToFile := prefLogToFile;
if not prefLogToFile then
ActivateFileLogging
else
DeactivateFileLogging;
// Save option
if prefLogToFile <> OldprefLogToFile then
begin
OpenRegistry;
MainReg.WriteBool('LogToFile', prefLogToFile);
end;
end;
{**
Open folder with session logs
}
procedure TMainForm.menuOpenLogFolderClick(Sender: TObject);
begin
ShellExec('', prefDirnameSessionLogs);
end;
{**
A header column of a VirtualTree was "dragged out", which means:
dragged down or up, not to the left or right.
We imitate the behaviour of various applications (fx Outlook) and
hide this dragged column
}
procedure TMainForm.vstHeaderDraggedOut(Sender: TVTHeader; Column:
TColumnIndex; DropPosition: TPoint);
begin
// Hide the draggedout column
Sender.Columns[Column].Options := Sender.Columns[Column].Options - [coVisible];
end;
procedure TMainForm.vstIncrementalSearch(Sender: TBaseVirtualTree; Node: PVirtualNode;
const SearchText: String; var Result: Integer);
var
CellText: String;
VT: TVirtualStringTree;
begin
// Override VT's default incremental search behaviour. Make it case insensitive.
VT := Sender as TVirtualStringTree;
if VT.FocusedColumn = NoColumn then
Exit;
CellText := VT.Text[Node, VT.FocusedColumn];
Result := StrLIComp(PChar(CellText), PChar(SearchText), Length(SearchText));
end;
{**
A cell in ListCommandStats gets painted.
Draw a progress bar on it to visualize its percentage value.
}
procedure TMainForm.ListCommandStatsBeforeCellPaint(Sender: TBaseVirtualTree;
TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
CellPaintMode: TVTCellPaintMode; CellRect: TRect; var ContentRect: TRect);
var
NodeData: PVTreeData;
begin
// Only paint bar in percentage column
if Column = 4 then begin
NodeData := Sender.GetNodeData(Node);
PaintColorBar(MakeFloat(NodeData.Captions[Column]), 100, TargetCanvas, CellRect);
end;
end;
procedure TMainForm.ListTablesBeforeCellPaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas;
Node: PVirtualNode; Column: TColumnIndex; CellPaintMode: TVTCellPaintMode; CellRect: TRect;
var ContentRect: TRect);
var
Obj: PDBObject;
begin
// Only paint bar in rows + size column
if Column in [1, 2] then begin
Obj := Sender.GetNodeData(Node);
case Column of
1: PaintColorBar(Obj.Rows, DBObjectsMaxRows, TargetCanvas, CellRect);
2: PaintColorBar(Obj.Size, DBObjectsMaxSize, TargetCanvas, CellRect);
end;
end;
end;
procedure TMainForm.ListProcessesBeforeCellPaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas;
Node: PVirtualNode; Column: TColumnIndex; CellPaintMode: TVTCellPaintMode; CellRect: TRect;
var ContentRect: TRect);
var
NodeData: PVTreeData;
begin
if Column = 5 then begin
NodeData := Sender.GetNodeData(Node);
PaintColorBar(MakeFloat(NodeData.Captions[Column]), ProcessListMaxTime, TargetCanvas, CellRect);
end;
end;
procedure TMainForm.PaintColorBar(Value, Max: Extended; TargetCanvas: TCanvas; CellRect: TRect);
var
BarWidth, CellWidth: Integer;
begin
if not prefDisplayBars then
Exit;
// Add minimal margin to cell edges
InflateRect(CellRect, -1, -1);
CellWidth := CellRect.Right - CellRect.Left;
BarWidth := Round(CellWidth / Max * Value);
if BarWidth > 0 then begin
TargetCanvas.Brush.Color := prefBarColor;
TargetCanvas.Pen.Color := ColorAdjustBrightness(TargetCanvas.Brush.Color, -40);
TargetCanvas.RoundRect(CellRect.Left, CellRect.Top, CellRect.Left+BarWidth, CellRect.Bottom, 2, 2);
end;
end;
{**
A row in the process list was selected. Fill SynMemoProcessView with
the SQL of that row.
}
procedure TMainForm.ListProcessesFocusChanged(Sender: TBaseVirtualTree;
Node: PVirtualNode; Column: TColumnIndex);
var
NodeData : PVTreeData;
enableSQLView : Boolean;
begin
enableSQLView := Assigned(Node);
SynMemoProcessView.Enabled := enableSQLView;
pnlProcessView.Enabled := enableSQLView;
if enableSQLView then begin
NodeData := ListProcesses.GetNodeData(Node);
SynMemoProcessView.Highlighter := SynSQLSyn1;
SynMemoProcessView.Text := NodeData.Captions[7];
SynMemoProcessView.Color := clWindow;
end else begin
SynMemoProcessView.Highlighter := nil;
SynMemoProcessView.Text := 'Please select a process in the above list.';
SynMemoProcessView.Color := clBtnFace;
end;
lblExplainProcess.Enabled := enableSQLView
and (UpperCase(GetFirstWord(SynMemoProcessView.Text)) <> 'SHOW')
and (SynMemoProcessView.GetTextLen > 0);
menuExplainProcess.Enabled := lblExplainProcess.Enabled;
end;
{***
Apply a filter to a Virtual Tree.
}
procedure TMainForm.editFilterVTChange(Sender: TObject);
begin
// Reset typing timer
TimerFilterVT.Enabled := False;
TimerFilterVT.Enabled := True;
editFilterVT.RightButton.Enabled := editFilterVT.Text <> '';
end;
procedure TMainForm.editFilterVTRightButtonClick(Sender: TObject);
begin
(Sender as TButtonedEdit).Clear;
end;
procedure TMainForm.TimerFilterVTTimer(Sender: TObject);
var
Node : PVirtualNode;
VT : TVirtualStringTree;
i : Integer;
match : Boolean;
search : String;
tab: TTabSheet;
VisibleCount: Cardinal;
CellText: String;
begin
// Disable timer to avoid filtering in a loop
TimerFilterVT.Enabled := False;
// Find the correct VirtualTree that shall be filtered
tab := PageControlMain.ActivePage;
if tab = tabHost then
tab := PageControlHost.ActivePage;
VT := nil;
if tab = tabDatabases then begin
VT := ListDatabases;
FilterTextDatabases := editFilterVT.Text;
end else if tab = tabVariables then begin
VT := ListVariables;
FilterTextVariables := editFilterVT.Text;
end else if tab = tabStatus then begin
VT := ListStatus;
FilterTextStatus := editFilterVT.Text;
end else if tab = tabProcesslist then begin
VT := ListProcesses;
FilterTextProcessList := editFilterVT.Text;
end else if tab = tabCommandStats then begin
VT := ListCommandStats;
FilterTextCommandStats := editFilterVT.Text;
end else if tab = tabDatabase then begin
VT := ListTables;
FilterTextDatabase := editFilterVT.Text;
end else if tab = tabData then begin
VT := DataGrid;
FilterTextData := editFilterVT.Text;
end else if QueryTabActive and (tab = ActiveQueryTab.TabSheet) then begin
VT := ActiveGrid;
ActiveQueryTab.FilterText := editFilterVT.Text;
end;
if not Assigned(VT) then
Exit;
// Loop through all nodes and hide non matching
Node := VT.GetFirst;
search := LowerCase( editFilterVT.Text );
VisibleCount := 0;
while Assigned(Node) do begin
// Don't filter anything if the filter text is empty
match := search = '';
// Search for given text in node's captions
if not match then for i := 0 to VT.Header.Columns.Count - 1 do begin
CellText := VT.Text[Node, i];
if Pos( search, LowerCase(CellText)) > 0 then begin
match := True;
break;
end;
end;
VT.IsVisible[Node] := match;
if match then
inc(VisibleCount);
Node := VT.GetNext(Node);
end;
if search <> '' then begin
lblFilterVTInfo.Caption := IntToStr(VisibleCount)+' out of '+IntToStr(VT.RootNodeCount)+' matching. '
+ IntToStr(VT.RootNodeCount - VisibleCount) + ' hidden.';
end else
lblFilterVTInfo.Caption := '';
VT.Repaint;
end;
procedure TMainForm.ListVariablesDblClick(Sender: TObject);
begin
menuEditVariable.Click;
end;
{**
Edit a server variable
}
procedure TMainForm.menuEditVariableClick(Sender: TObject);
var
NodeData: PVTreeData;
begin
if EditVariableForm = nil then
EditVariableForm := TfrmEditVariable.Create(Self);
NodeData := ListVariables.GetNodeData(ListVariables.FocusedNode);
EditVariableForm.VarName := NodeData.Captions[0];
EditVariableForm.VarValue := NodeData.Captions[1];
// Refresh relevant list node
if EditVariableForm.ShowModal = mrOK then
NodeData.Captions[1] := Connection.GetVar('SHOW VARIABLES LIKE '+esc(NodeData.Captions[0]), 1);
end;
{**
The database tree doesn't use any structure for its nodes.
}
procedure TMainForm.DBtreeGetNodeDataSize(Sender: TBaseVirtualTree; var
NodeDataSize: Integer);
begin
NodeDataSize := 0;
end;
{**
Set text of a treenode before it gets displayed or fetched in any way
}
procedure TMainForm.DBtreeGetText(Sender: TBaseVirtualTree; Node:
PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var CellText: String);
var
DBObjects: TDBObjectList;
db: String;
i: Integer;
Bytes: Int64;
AllListsCached: Boolean;
begin
case Column of
0: case Sender.GetNodeLevel(Node) of
0: CellText := Connection.Parameters.Username + '@' + Connection.Parameters.Hostname;
1: CellText := AllDatabases[Node.Index];
2: begin
DBObjects := Connection.GetDBObjects(AllDatabases[Node.Parent.Index]);
CellText := DBObjects[Node.Index].Name;
end;
end;
1: case GetTreeNodeType(Sender, Node) of
// Calculate and display the sum of all table sizes in ALL dbs if all table lists are cached
lntNone: begin
AllListsCached := true;
for i:=0 to AllDatabases.Count-1 do begin
if not Connection.DbObjectsCached(AllDatabases[i]) then begin
AllListsCached := false;
break;
end;
end;
// Will be also set to a negative value by GetTableSize and results of SHOW TABLES
Bytes := -1;
if AllListsCached then begin
Bytes := 0;
for i:=0 to AllDatabases.Count-1 do begin
DBObjects := Connection.GetDBObjects(AllDatabases[i]);
Inc(Bytes, DBObjects.DataSize);
end;
end;
if Bytes >= 0 then CellText := FormatByteNumber(Bytes)
else CellText := '';
end;
// Calculate and display the sum of all table sizes in ONE db, if the list is already cached.
lntDb: begin
db := (Sender as TVirtualStringTree).Text[Node, 0];
if not Connection.DbObjectsCached(db) then
CellText := ''
else begin
DBObjects := Connection.GetDBObjects(db);
CellText := FormatByteNumber(DBObjects.DataSize);
end;
end;
lntTable: begin
db := (Sender as TVirtualStringTree).Text[Node.Parent, 0];
DBObjects := Connection.GetDBObjects(db);
CellText := FormatByteNumber(DBObjects[Node.Index].Size);
end
else CellText := ''; // Applies for views and crashed tables
end;
end;
end;
{**
Set icon of a treenode before it gets displayed
}
procedure TMainForm.DBtreeGetImageIndex(Sender: TBaseVirtualTree; Node:
PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex; var Ghosted:
Boolean; var ImageIndex: Integer);
var
DBObjects: TDBObjectList;
vt: TVirtualStringTree;
db: String;
begin
if Column > 0 then
Exit;
// Prevent state images, overlaying the normal image
if not (Kind in [ikNormal, ikSelected]) then Exit;
vt := Sender as TVirtualStringTree;
case Sender.GetNodeLevel(Node) of
0: ImageIndex := ICONINDEX_SERVER;
1: begin
db := vt.Text[Node, 0];
if ActiveDatabase = db then
ImageIndex := ICONINDEX_DB_HIGHLIGHT
else
ImageIndex := ICONINDEX_DB;
Ghosted := not Connection.DbObjectsCached(db);
end;
2: begin
DBObjects := Connection.GetDBObjects(AllDatabases[Node.Parent.Index]);
// Various bug reports refer to this location where we reference a db object which is outside the range
// of DBObjects. Probably a timing issue. Work around that by doing a safety check here.
if Node.Index >= Cardinal(DBObjects.Count) then
Exit;
ImageIndex := DBObjects[Node.Index].ImageIndex;
end;
end;
end;
{**
Set childcount of an expanding treenode
}
procedure TMainForm.DBtreeInitChildren(Sender: TBaseVirtualTree; Node: PVirtualNode; var ChildCount: Cardinal);
var
VT: TVirtualStringTree;
begin
VT := Sender as TVirtualStringTree;
case VT.GetNodeLevel(Node) of
// Root node has only one single child (user@host)
0: begin
Screen.Cursor := crHourglass;
ShowStatusMsg('Reading Databases...');
if VT.Tag = VTREE_NOTLOADED_PURGECACHE then try
AllDatabases := Connection.AllDatabases;
except
on E:Exception do begin
AllDatabases.Clear;
MessageDlg(E.Message+CRLF+CRLF+'You have no privilege to execute SHOW DATABASES. Please specify one or more databases in your session settings, if you want to see any.', mtError, [mbOK], 0);
end;
end;
ShowStatusMsg(STATUS_MSG_READY);
VT.Tag := VTREE_LOADED;
InvalidateVT(ListDatabases, VTREE_NOTLOADED, False);
ChildCount := AllDatabases.Count;
Screen.Cursor := crDefault;
end;
// DB node expanding
1: begin
Screen.Cursor := crHourglass;
ShowStatusMsg( 'Reading objects ...' );
try
ChildCount := Connection.GetDBObjects(AllDatabases[Node.Index]).Count;
finally
ShowStatusMsg( STATUS_MSG_READY );
Screen.Cursor := crDefault;
end;
end;
else Exit;
end;
end;
{**
Set initial options of a treenode
}
procedure TMainForm.DBtreeInitNode(Sender: TBaseVirtualTree; ParentNode, Node:
PVirtualNode; var InitialStates: TVirtualNodeInitStates);
var
level: Cardinal;
begin
level := Sender.GetNodeLevel(Node);
// Ensure plus sign is visible for root and dbs
if level in [0,1] then
Include( InitialStates, ivsHasChildren);
// Host node is always expanded
if level = 0 then
Include( InitialStates, ivsExpanded );
end;
{**
Selection in database tree has changed
}
procedure TMainForm.DBtreeFocusChanged(Sender: TBaseVirtualTree;
Node: PVirtualNode; Column: TColumnIndex);
var
newDb, oldDb, newDbObject: String;
begin
debug('DBtreeFocusChanged()');
SelectedTableCreateStatement := '';
if not Assigned(Node) then
Exit;
if not FProcessDBtreeFocusChanges then
Exit;
// Post pending UPDATE
if DataGridHasChanges then
actDataPostChangesExecute(Sender);
case Sender.GetNodeLevel(Node) of
0: begin
if (not DBtree.Dragging) and (not QueryTabActive) then begin
PageControlMain.ActivePage := tabHost;
PageControlMain.OnChange(Sender);
PageControlHost.ActivePage := tabDatabases;
end;
tabDatabase.TabVisible := False;
tabEditor.TabVisible := False;
tabData.TabVisible := False;
end;
1: begin
newDb := AllDatabases[Node.Index];
// Selecting a database can cause an SQL error if the db was deleted from outside. Select previous node in that case.
try
Connection.Database := newDb;
except on E:Exception do begin
MessageDlg(E.Message, mtError, [mbOK], 0);
SelectNode(DBtree, PreviousFocusedNode);
Exit;
end;
end;
if (not DBtree.Dragging) and (not QueryTabActive) then begin
PageControlMain.ActivePage := tabDatabase;
PageControlMain.OnChange(Sender);
end;
tabDatabase.TabVisible := true;
tabEditor.TabVisible := false;
tabData.TabVisible := false;
end;
2: begin
newDb := AllDatabases[Node.Parent.Index];
try
Connection.Database := newDb;
except on E:Exception do begin
MessageDlg(E.Message, mtError, [mbOK], 0);
SelectNode(DBtree, PreviousFocusedNode);
Exit;
end;
end;
newDbObject := SelectedTable.Name;
tabEditor.TabVisible := SelectedTable.NodeType in [lntTable, lntView, lntProcedure, lntFunction, lntTrigger, lntEvent];
tabData.TabVisible := SelectedTable.NodeType in [lntTable, lntView];
ParseSelectedTableStructure;
if tabEditor.TabVisible then begin
actEditObjectExecute(Sender);
// When a table is clicked in the tree, and the current
// tab is a Host or Database tab, switch to showing table columns.
if (PagecontrolMain.ActivePage = tabHost) or (PagecontrolMain.ActivePage = tabDatabase) then begin
PagecontrolMain.ActivePage := tabEditor;
PageControlMain.OnChange(Sender);
end;
InvalidateVT(DataGrid, VTREE_NOTLOADED, False);
// When a table is clicked in the tree, and the query
// tab is active, update the list of columns
if QueryTabActive then
RefreshQueryHelpers;
end;
end;
end;
if Assigned(PreviousFocusedNode) then case DBTree.GetNodeLevel(PreviousFocusedNode) of
0: oldDb := '';
1: oldDb := DBTree.Text[PreviousFocusedNode, 0];
2: oldDb := DBTree.Text[PreviousFocusedNode.Parent, 0];
end;
if newDb <> oldDb then begin
tabDatabase.Caption := sstr('Database: ' + newDb, 30);
ListTables.ClearSelection;
ListTables.FocusedNode := nil;
InvalidateVT(ListTables, VTREE_NOTLOADED, False);
end;
PreviousFocusedNode := DBTree.FocusedNode;
FixQueryTabCloseButtons;
SetWindowCaption;
end;
procedure TMainForm.DBtreeFocusChanging(Sender: TBaseVirtualTree; OldNode,
NewNode: PVirtualNode; OldColumn, NewColumn: TColumnIndex;
var Allowed: Boolean);
begin
debug('DBtreeFocusChanging');
// Check if some editor has unsaved changes
if Assigned(ActiveObjectEditor) and Assigned(NewNode) and (NewNode <> OldNode) and FProcessDBtreeFocusChanges then begin
Allowed := not (ActiveObjectEditor.DeInit in [mrAbort, mrCancel]);
DBTree.Selected[DBTree.FocusedNode] := not Allowed;
end else
Allowed := True;
end;
procedure TMainForm.DBtreeFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
begin
// Keep track of the previously selected tree node's state, to avoid AVs in OnFocusChanged()
if Node = PreviousFocusedNode then
PreviousFocusedNode := nil;
end;
procedure TMainForm.ParseSelectedTableStructure;
begin
SelectedTableColumns.Clear;
SelectedTableKeys.Clear;
SelectedTableForeignKeys.Clear;
InvalidateVT(DataGrid, VTREE_NOTLOADED_PURGECACHE, False);
try
case SelectedTable.NodeType of
lntTable: begin
SelectedTableCreateStatement := Connection.GetVar('SHOW CREATE TABLE '+Mainform.mask(SelectedTable.Name), 1);
ParseTableStructure(SelectedTableCreateStatement, SelectedTableColumns, SelectedTableKeys, SelectedTableForeignKeys);
end;
lntView: ParseViewStructure(SelectedTable.Name, SelectedTableColumns);
end;
except on E:Exception do
MessageDlg(E.Message, mtError, [mbOK], 0);
end;
end;
procedure TMainForm.DatabaseChanged(Database: String);
begin
if (Database='') or (AllDatabases.IndexOf(Database) > -1) then
ActiveDatabase := Database;
end;
procedure TMainForm.DBObjectsCleared(Database: String);
var
Node: PVirtualNode;
WasExpanded: Boolean;
begin
// Avoid AVs while processing FormDestroy
if csDestroying in ComponentState then
Exit;
// Reload objects in ListTables
if ActiveDatabase=Database then
InvalidateVT(ListTables, VTREE_NOTLOADED, False);
// Reload objects for database tree
Node := DBtree.GetFirstChild(DBtree.GetFirst);
while Assigned(Node) do begin
if Database = DBtree.Text[Node, 0] then begin
WasExpanded := DBtree.Expanded[Node];
DBtree.ResetNode(Node);
DBtree.Expanded[Node] := WasExpanded;
break;
end;
Node := DBtree.GetNextSibling(Node);
end;
ActiveDatabase := Database;
end;
procedure TMainForm.DBtreeDblClick(Sender: TObject);
var
Node: PVirtualNode;
m: TSynMemo;
begin
// Paste DB or table name into query window on treeview double click.
Node := DBtree.FocusedNode;
if not Assigned(Node) then Exit;
if DBtree.GetNodeLevel(Node) = 0 then Exit;
if not QueryTabActive then Exit;
m := ActiveQueryMemo;
m.DragDrop(Sender, m.CaretX, m.CaretY);
end;
procedure TMainForm.DBtreePaintText(Sender: TBaseVirtualTree; const
TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; TextType:
TVSTTextType);
begin
// Grey out rather unimportant "Size" column
if Column <> 1 then
Exit;
case Sender.GetNodeLevel(Node) of
0, 1: TargetCanvas.Font.Color := clWindowText;
2: TargetCanvas.Font.Color := $00444444;
end;
end;
{**
Refresh the whole tree
}
procedure TMainForm.RefreshTree(DoResetTableCache: Boolean; SelectDatabase: String = '');
var
oldActiveDatabase, oldSelectedTableName: String;
oldSelectedTableType: TListNodeType;
begin
// Remember currently active database and table
debug('RefreshTree()');
oldActiveDatabase := ActiveDatabase;
oldSelectedTableName := SelectedTable.Name;
oldSelectedTableType := SelectedTable.NodeType;
DBtree.FocusedNode := nil;
// ReInit tree population
if DoResetTableCache then
Connection.ClearAllDbObjects;
InvalidateVT(DBtree, VTREE_NOTLOADED_PURGECACHE, True);
// Reselect active or new database if present. Could have been deleted or renamed.
try
if SelectDatabase <> '' then
ActiveDatabase := SelectDatabase
else if oldActiveDatabase <> '' then
ActiveDatabase := oldActiveDatabase;
if oldSelectedTableName <> '' then
SelectDBObject(oldSelectedTableName, oldSelectedTableType);
except
end;
// Select "host" node if database was deleted outside and node is gone
if not Assigned(DBtree.FocusedNode) then
SelectNode(DBtree, DBtree.GetFirst);
end;
{**
Refresh one database node in the db tree
}
procedure TMainForm.RefreshActiveTreeDB(FocusObject: TDBObject);
var
ObjNode: PVirtualNode;
Objects: TDBObjectList;
begin
FProcessDBtreeFocusChanges := False;
try
Connection.ClearDbObjects(ActiveDatabase);
// Set focused node
if FocusObject <> nil then begin
Objects := Connection.GetDBObjects(ActiveDatabase);
ObjNode := DBtree.GetFirstChild(FindDBNode(ActiveDatabase));
while Assigned(ObjNode) do begin
if (Objects[ObjNode.Index].Name = FocusObject.Name)
and (Objects[ObjNode.Index].NodeType = FocusObject.NodeType) then begin
SelectNode(DBtree, ObjNode);
end;
ObjNode := DBtree.GetNextSibling(ObjNode);
end;
end;
finally
FProcessDBtreeFocusChanges := True;
end;
end;
{**
Find a database node in the tree by passing its name
}
function TMainForm.FindDBNode(db: String): PVirtualNode;
var
i, s: Integer;
n: PVirtualNode;
begin
Result := nil;
// TStringList.CaseSensitive= True|False is only used in .IndexOf and .Sort procs,
// it does not avoid or remove duplicate items
AllDatabases.CaseSensitive := True;
s := AllDatabases.IndexOf(db);
if s = -1 then begin
AllDatabases.CaseSensitive := False;
s := AllDatabases.IndexOf(db);
end;
if s > -1 then begin
n := DBtree.GetFirstChild(DBtree.GetFirst);
for i := 0 to DBtree.GetFirst.ChildCount - 1 do begin
if Integer(n.Index) = s then begin
Result := n;
Exit;
end;
n := DBtree.GetNextSibling(n);
end;
end;
end;
{**
Expand all db nodes
}
procedure TMainForm.menuTreeExpandAllClick(Sender: TObject);
begin
DBtree.FullExpand;
DBtree.ScrollIntoView(DBtree.FocusedNode, False);
end;
{**
Collapse all db nodes
}
procedure TMainForm.menuTreeCollapseAllClick(Sender: TObject);
var
n: PVirtualNode;
i: Integer;
begin
n := DBtree.GetFirstChild(DBtree.GetFirst);
for i := 0 to DBtree.GetFirst.ChildCount - 1 do begin
DBtree.FullCollapse(n);
n := DBtree.GetNextSibling(n);
end;
DBtree.ScrollIntoView(DBtree.FocusedNode, False);
end;
procedure TMainForm.editFilterSearchChange(Sender: TObject);
var
Add, Clause: String;
i: Integer;
ed: TEdit;
Col: TTableColumn;
begin
ed := TEdit(Sender);
Clause := '';
Add := '';
if ed.Text <> '' then begin
for i:=0 to SelectedTableColumns.Count-1 do begin
Col := TTableColumn(SelectedTableColumns[i]);
if i > 0 then
Add := Add + ' OR ';
Add := Add + mask(Col.Name) + ' LIKE ' + esc('%'+ed.Text+'%');
if Length(Add) > 45 then begin
Clause := Clause + Add + CRLF;
Add := '';
end;
end;
if Add <> '' then
Clause := Clause + Add;
end;
SynMemoFilter.UndoList.AddGroupBreak;
SynMemoFilter.SelectAll;
SynMemoFilter.SelText := Clause;
end;
procedure TMainForm.SynMemoFilterStatusChange(Sender: TObject; Changes: TSynStatusChanges);
begin
actClearFilterEditor.Enabled := (Sender as TSynMemo).GetTextLen > 0;
end;
procedure TMainForm.ToggleFilterPanel(ForceVisible: Boolean = False);
var
ShowIt: Boolean;
begin
ShowIt := ForceVisible or (not pnlFilter.Visible);
tbtnDataFilter.Down := ShowIt;
pnlFilter.Visible := ShowIt;
end;
procedure TMainForm.editFilterSearchEnter(Sender: TObject);
begin
// Enables triggering apply button with Enter
btnFilterApply.Default := True;
end;
procedure TMainForm.editFilterSearchExit(Sender: TObject);
begin
btnFilterApply.Default := False;
end;
{**
A grid cell fetches its text content
}
procedure TMainForm.AnyGridGetText(Sender: TBaseVirtualTree; Node:
PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var CellText: String);
var
c: PGridCell;
gr: TGridResult;
EditingCell: Boolean;
begin
if Column = -1 then
Exit;
gr := GridResult(Sender);
c := @gr.Rows[Node.Index].Cells[Column];
EditingCell := Sender.IsEditing and (Node = Sender.FocusedNode) and (Column = Sender.FocusedColumn);
if c.Modified then begin
if c.NewIsNull then begin
if EditingCell then CellText := ''
else CellText := TEXT_NULL;
end else CellText := c.NewText;
end else begin
if c.IsNull then begin
if EditingCell then CellText := ''
else CellText := TEXT_NULL;
end else begin
CellText := c.Text;
if (Sender = DataGrid) and (not DataGridRowHasFullData(Node)) and (Length(c.Text) = GRIDMAXDATA) then
CellText := CellText + ' [...]';
end;
end;
end;
procedure TMainForm.CalcNullColors;
var
i: Integer;
h, l, s: Word;
begin
for i:=Low(DatatypeCategories) to High(DatatypeCategories) do begin
ColorRGBToHLS(DatatypeCategories[i].Color, h, l, s);
Inc(l, COLORSHIFT_NULLFIELDS);
s := Max(0, s-2*COLORSHIFT_NULLFIELDS);
DatatypeCategories[i].NullColor := ColorHLSToRGB(h, l, s);
end;
end;
{**
Cell in data- or query grid gets painted. Colorize font. This procedure is
called extremely often for repainting the grid cells. Keep it highly optimized.
}
procedure TMainForm.AnyGridPaintText(Sender: TBaseVirtualTree; const
TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; TextType:
TVSTTextType);
var
cl: TColor;
r: TGridResult;
begin
if Column = -1 then
Exit;
r := GridResult(Sender);
// Make primary key columns bold
if r.Columns[Column].IsPriPart then
TargetCanvas.Font.Style := TargetCanvas.Font.Style + [fsBold];
// Do not apply any color on a selected, highlighted cell to keep readability
if (vsSelected in Node.States) and (Node = Sender.FocusedNode) and (Column = Sender.FocusedColumn) then
cl := clHighlightText
else if vsSelected in Node.States then
cl := clBlack
else if r.Rows[Node.Index].Cells[Column].IsNull then
cl := DatatypeCategories[Integer(r.Columns[Column].DatatypeCat)].NullColor
else
cl := DatatypeCategories[Integer(r.Columns[Column].DatatypeCat)].Color;
TargetCanvas.Font.Color := cl;
end;
procedure TMainForm.DataGridAfterCellPaint(Sender: TBaseVirtualTree;
TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
CellRect: TRect);
begin
// Don't waist time
if Column = NoColumn then Exit;
// Paint a red triangle at the top left corner of the cell
if DataGridResult.Rows[Node.Index].Cells[Column].Modified then
ImageListMain.Draw(TargetCanvas, CellRect.Left, CellRect.Top, 111);
end;
{**
Header column in datagrid clicked.
Left button: handle ORDER BY
Right button: show column selection box
}
procedure TMainForm.DataGridHeaderClick(Sender: TVTHeader; HitInfo: TVTHeaderHitInfo);
var
frm: TForm;
i, j, LeftColPos: Integer;
columnexists : Boolean;
ColName: String;
begin
if HitInfo.Column = NoColumn then
Exit;
if HitInfo.Button = mbLeft then begin
ColName := Sender.Columns[HitInfo.Column].Text;
// Add a new order column after a columns title has been clicked
// Check if order column is already existant
columnexists := False;
for i := Low(DataGridSortColumns) to High(DataGridSortColumns) do begin
if DataGridSortColumns[i].ColumnName = ColName then begin
// AddOrderCol is already in the list. Switch its direction:
// ASC > DESC > [delete col]
columnexists := True;
if DataGridSortColumns[i].SortDirection = ORDER_ASC then
DataGridSortColumns[i].SortDirection := ORDER_DESC
else begin
// Delete order col
for j := i to High(DataGridSortColumns) - 1 do
DataGridSortColumns[j] := DataGridSortColumns[j+1];
SetLength(DataGridSortColumns, Length(DataGridSortColumns)-1);
end;
// We found the matching column, no need to loop further
break;
end;
end;
if not columnexists then begin
i := Length(DataGridSortColumns);
SetLength(DataGridSortColumns, i+1);
DataGridSortColumns[i] := TOrderCol.Create;
DataGridSortColumns[i].ColumnName := ColName;
DataGridSortColumns[i].SortDirection := ORDER_ASC;
end;
// Refresh grid, and restore X scroll offset, so the just clicked column is still at the same place.
LeftColPos := Sender.Columns[HitInfo.Column].Left;
InvalidateVT(DataGrid, VTREE_NOTLOADED_PURGECACHE, True);
Sender.Treeview.OffsetX := -(Sender.Columns[HitInfo.Column].Left - Sender.Treeview.OffsetX - LeftColPos);
end else begin
frm := TColumnSelectionForm.Create(self);
// Position new form relative to btn's position
frm.Top := HitInfo.Y + DataGrid.ClientOrigin.Y - Integer(DataGrid.Header.Height);
frm.Left := HitInfo.X + DataGrid.ClientOrigin.X;
// Display form
frm.Show;
end;
end;
{**
Only allow grid editing if there is a good key available
}
procedure TMainForm.setNULL1Click(Sender: TObject);
begin
// Internally calls OnNewText event:
DataGrid.Text[DataGrid.FocusedNode, DataGrid.FocusedColumn] := '';
DataGridResult.Rows[DataGrid.FocusedNode.Index].Cells[DataGrid.FocusedColumn].NewIsNull := True;
DataGrid.RepaintNode(DataGrid.FocusedNode);
end;
{**
Content of a grid cell was modified
}
procedure TMainForm.DataGridNewText(Sender: TBaseVirtualTree; Node:
PVirtualNode; Column: TColumnIndex; NewText: String);
var
Row: PGridRow;
begin
Row := @DataGridResult.Rows[Node.Index];
// Remember new value
Row.Cells[Column].NewText := NewText;
Row.Cells[Column].NewIsNull := False;
Row.Cells[Column].Modified := True;
// Set state of row for UPDATE mode, don't touch grsInserted
if Row.State = grsDefault then
DataGridResult.Rows[Node.Index].State := grsModified;
DataGridHasChanges := True;
ValidateControls(Sender);
end;
{**
DataGrid: node focus has changed
}
procedure TMainForm.DataGridChange(Sender: TBaseVirtualTree; Node:
PVirtualNode);
begin
ValidateControls(Sender);
end;
{**
DataGrid: node and/or column focus is about to change. See if we allow that.
}
procedure TMainForm.DataGridFocusChanging(Sender: TBaseVirtualTree; OldNode,
NewNode: PVirtualNode; OldColumn, NewColumn: TColumnIndex; var Allowed:
Boolean);
begin
// Detect changed focus and update row
if Assigned(OldNode) and (OldNode <> NewNode) then
Allowed := DataGridPostUpdateOrInsert(OldNode)
else
Allowed := True;
end;
procedure TMainForm.DataGridFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex);
begin
UpdateLineCharPanel;
end;
{**
DataGrid: invoke update or insert routine
}
function TMainForm.DataGridPostUpdateOrInsert(Node: PVirtualNode): Boolean;
begin
Result := True;
if not Assigned(Node) then
Exit;
if Cardinal(High(DataGridResult.Rows)) >= Node.Index then
case DataGridResult.Rows[Node.Index].State of
grsModified: Result := GridPostUpdate(DataGrid);
grsInserted: Result := GridPostInsert(DataGrid);
end;
end;
{**
DataGrid: compose and fire UPDATE query
}
function TMainForm.GridPostUpdate(Sender: TBaseVirtualTree): Boolean;
var
i: Integer;
sql, Val: String;
Row: PGridRow;
begin
sql := 'UPDATE '+mask(DataGridTable)+' SET';
Row := @DataGridResult.Rows[Sender.FocusedNode.Index];
for i := 0 to Length(DataGridResult.Columns) - 1 do begin
if Row.Cells[i].Modified then begin
Val := Row.Cells[i].NewText;
case DataGridResult.Columns[i].DatatypeCat of
dtcInteger, dtcReal: Val := UnformatNumber(Val);
dtcBinary, dtcSpatial: begin
if actBlobAsText.Checked then
Val := esc(Val)
else begin
CheckHex(Copy(Val, 3), 'Invalid hexadecimal string given in field "' + DataGridResult.Columns[i].Name + '".');
if Val = '0x' then
Val := esc('');
end;
end;
else Val := esc(Val);
end;
if Row.Cells[i].NewIsNull then Val := 'NULL';
sql := sql + ' ' + mask(DataGridResult.Columns[i].Name) + '=' + Val + ', ';
end;
end;
// Cut trailing comma
sql := Copy(sql, 1, Length(sql)-2);
sql := sql + ' WHERE ' + GetWhereClause(Row, @DataGridResult.Columns) + ' LIMIT 1';
try
// Send UPDATE query
Connection.Query(sql);
if Connection.RowsAffected = 0 then begin
MessageDlg('Your change did not affect any row! This can have several causes:' + CRLF + CRLF +
'a) Your changes were silently converted by the server. For instance, if you tried to ' +
'update an unsigned TINYINT field from its maximum value 255 to a higher value.' + CRLF + CRLF +
'b) The server could not find the source row because it was deleted ' +
'from outside.' + CRLF + CRLF +
'c) The server could not find the source row because its primary key fields were modified ' +
'from outside.',
mtInformation, [mbOK], 0);
end;
Result := True;
except
on E:Exception do begin
MessageDlg(E.Message, mtError, [mbOK], 0);
Result := False;
end;
end;
if Result then begin
// Reselect just updated row in grid from server to ensure displaying
// correct values which were silently converted by the server
for i := 0 to Length(DataGridResult.Columns) - 1 do begin
if not Row.Cells[i].Modified then
Continue;
Row.Cells[i].Text := Row.Cells[i].NewText;
Row.Cells[i].IsNull := Row.Cells[i].NewIsNull;
end;
GridFinalizeEditing(Sender);
end;
end;
{**
Repaint edited node and reset state of grid row
}
procedure TMainForm.GridFinalizeEditing(Sender: TBaseVirtualTree);
var
i, c: Integer;
begin
c := Sender.FocusedNode.Index;
DataGridResult.Rows[c].State := grsDefault;
for i := 0 to Length(DataGridResult.Rows[c].Cells) - 1 do begin
DataGridResult.Rows[c].Cells[i].NewText := '';
DataGridResult.Rows[c].Cells[i].Modified := False;
end;
Sender.RepaintNode(Sender.FocusedNode);
DataGridHasChanges := False;
ValidateControls(Sender);
end;
{**
Compose a WHERE clause used for UPDATEs and DELETEs
}
function TMainForm.GetWhereClause(Row: PGridRow; Columns: PGridColumns): String;
var
i, j: Integer;
KeyVal: String;
KeyCols: TStringList;
begin
Result := '';
KeyCols := GetKeyColumns;
if KeyCols.Count = 0 then begin
// No key present - use all columns in that case
for i:=0 to SelectedTableColumns.Count-1 do
KeyCols.Add(SelectedTableColumns[i].Name);
end;
for i := 0 to KeyCols.Count - 1 do begin
for j := 0 to Length(Columns^) - 1 do begin
if Columns^[j].Name = KeyCols[i] then
break;
end;
// Find old value of key column
KeyVal := Row.Cells[j].Text;
// Quote if needed
case DataGridResult.Columns[j].DatatypeCat of
dtcInteger, dtcReal: KeyVal := UnformatNumber(KeyVal);
dtcBinary, dtcSpatial: begin
if actBlobAsText.Checked then
KeyVal := esc(KeyVal)
else if KeyVal = '0x' then
KeyVal := esc('');
end
else KeyVal := esc(KeyVal);
end;
if Row.Cells[j].IsNull then KeyVal := ' IS NULL'
else KeyVal := '=' + KeyVal;
Result := Result + mask(KeyCols[i]) + KeyVal + ' AND ';
end;
// Cut trailing AND
Result := Copy(Result, 1, Length(Result)-5);
end;
{**
Find key columns for a WHERE clause by analysing a SHOW KEYS FROM ... resultset
}
function TMainForm.GetKeyColumns: TStringList;
var
i, j, k: Integer;
AllowsNull: Boolean;
Key: TTableKey;
Col: TTableColumn;
begin
Result := TStringList.Create;
// Find best key for updates
// 1. round: find a primary key
for i:=0 to SelectedTableKeys.Count-1 do begin
Key := TTableKey(SelectedTableKeys[i]);
if Key.Name = 'PRIMARY' then begin
Result := Key.Columns;
Exit;
end;
end;
// no primary key available -> 2. round: find a unique key
for i:=0 to SelectedTableKeys.Count-1 do begin
Key := TTableKey(SelectedTableKeys[i]);
if Key.IndexType = UKEY then begin
// We found a UNIQUE key - better than nothing. Check if one of the key
// columns allows NULLs which makes it dangerous to use in UPDATES + DELETES.
AllowsNull := False;
for j:=0 to Key.Columns.Count-1 do begin
for k:=0 to SelectedTableColumns.Count-1 do begin
Col := TTableColumn(SelectedTableColumns[k]);
if Col.Name = Key.Columns[j] then
AllowsNull := Col.AllowNull;
if AllowsNull then break;
end;
if AllowsNull then break;
end;
if not AllowsNull then begin
Result := Key.Columns;
break;
end;
end;
end;
end;
{**
DataGrid: compose and fire UPDATE query
}
procedure TMainForm.DataGridInsertRow(CopyValuesFromNode: PVirtualNode);
var
i, j: Integer;
OldRow: TGridRow;
begin
// Scroll to the bottom to ensure we append the new row at the very last DataGridResult chunk
DataGrid.FocusedNode := DataGrid.GetLast;
DataGrid.Repaint;
// Steeling focus now to invoke posting a pending row update
DataGrid.FocusedNode := nil;
i := Length(DataGridResult.Rows);
SetLength(DataGridResult.Rows, i+1);
SetLength(DataGridResult.Rows[i].Cells, Length(DataGridResult.Columns));
DataGridResult.Rows[i].State := grsInserted;
for j:=0 to Length(DataGridResult.Rows[i].Cells)-1 do begin
DataGridResult.Rows[i].Cells[j].Text := '';
end;
if Assigned(CopyValuesFromNode) then begin
// Copy values from source row, ensure we have whole cell data
OldRow := DataGridResult.Rows[CopyValuesFromNode.Index];
for j:=0 to DataGrid.Header.Columns.Count-1 do begin
if not (coVisible in DataGrid.Header.Columns[j].Options) then
continue; // Ignore invisible key column
if SelectedTableColumns[j].DefaultType = cdtAutoInc then
continue; // Empty value for auto-increment column
DataGridResult.Rows[i].Cells[j].NewText := OldRow.Cells[j].Text;
DataGridResult.Rows[i].Cells[j].NewIsNull := OldRow.Cells[j].IsNull;
DataGridResult.Rows[i].Cells[j].Modified := (DataGridResult.Rows[i].Cells[j].NewText <> DataGridResult.Rows[i].Cells[j].Text)
or (DataGridResult.Rows[i].Cells[j].NewIsNull <> DataGridResult.Rows[i].Cells[j].IsNull);
end;
end;
DataGrid.FocusedNode := DataGrid.AddChild(nil);
DataGrid.ClearSelection;
DataGrid.Selected[DataGrid.FocusedNode] := True;
DataGridHasChanges := True;
ValidateControls(DataGrid);
end;
{**
DataGrid: compose and fire INSERT query
}
function TMainForm.GridPostInsert(Sender: TBaseVirtualTree): Boolean;
var
Row: PGridRow;
sql, Cols, Val, Vals: String;
i: Integer;
Node: PVirtualNode;
begin
Node := Sender.FocusedNode;
Row := @DataGridResult.Rows[Node.Index];
Cols := '';
Vals := '';
for i := 0 to Length(DataGridResult.Columns) - 1 do begin
if Row.Cells[i].Modified then begin
Cols := Cols + mask(DataGridResult.Columns[i].Name) + ', ';
Val := Row.Cells[i].NewText;
case DataGridResult.Columns[i].DatatypeCat of
dtcInteger, dtcReal: Val := UnformatNumber(Val);
dtcBinary, dtcSpatial: begin
if actBlobAsText.Checked then
Val := esc(Val)
else begin
CheckHex(Copy(Val, 3), 'Invalid hexadecimal string given in field "' + DataGridResult.Columns[i].Name + '".');
if Val = '0x' then
Val := esc('');
end;
end;
else Val := esc(Val);
end;
if Row.Cells[i].NewIsNull then Val := 'NULL';
Vals := Vals + Val + ', ';
end;
end;
if Length(Cols) = 0 then begin
// No field was manually modified, cancel the INSERT in that case
Sender.BeginUpdate;
Sender.DeleteNode(Node);
SetLength(DataGridResult.Rows, Length(DataGridResult.Rows) - 1);
Sender.EndUpdate;
DataGridHasChanges := False;
ValidateControls(Sender);
Result := True; // Important for DataGridFocusChanging to allow moving focus
end else begin
// At least one field was modified, assume this INSERT should be posted
Vals := Copy(Vals, 1, Length(Vals)-2);
Cols := Copy(Cols, 1, Length(Cols)-2);
sql := 'INSERT INTO '+mask(DataGridTable)+' ('+Cols+') VALUES ('+Vals+')';
// Send INSERT query
try
Connection.Query(sql);
if Connection.RowsAffected = 0 then
Raise Exception.Create('Server failed to insert row.');
Result := True;
GridFinalizeEditing(Sender);
InvalidateVT(DataGrid, VTREE_NOTLOADED_PURGECACHE, False);
except
on E:Exception do begin
MessageDlg(E.Message, mtError, [mbOK], 0);
Result := False;
end;
end;
end;
end;
{**
DataGrid: compose and fire DELETE query
}
function TMainForm.GridPostDelete(Sender: TBaseVirtualTree): Boolean;
var
Node, FocusAfterDelete: PVirtualNode;
Nodes: TNodeArray;
sql: String;
Affected: Int64;
i, j: Integer;
msg: String;
begin
Node := Sender.GetFirstSelected;
FocusAfterDelete := nil;
sql := 'DELETE FROM '+mask(SelectedTable.Name)+' WHERE';
while Assigned(Node) do begin
sql := sql + ' (' +
GetWhereClause(@DataGridResult.Rows[Node.Index], @DataGridResult.Columns) +
') OR';
FocusAfterDelete := Node;
Node := Sender.GetNextSelected(Node);
end;
if Assigned(FocusAfterDelete) then
FocusAfterDelete := Sender.GetNext(FocusAfterDelete);
sql := Copy(sql, 1, Length(sql)-3);
sql := sql + ' LIMIT ' + IntToStr(Sender.SelectedCount);
try
// Send DELETE query
Connection.Query(sql);
Result := True;
except
on E:Exception do begin
MessageDlg(E.Message, mtError, [mbOK], 0);
Result := False;
end;
end;
if Result then begin
// Remove deleted row nodes out of the grid
Affected := Connection.RowsAffected;
if Affected = Sender.SelectedCount then begin
// Fine. Number of deleted rows equals the selected node count.
// In this case, just remove the selected nodes, avoid a full reload
Sender.BeginUpdate;
Nodes := Sender.GetSortedSelection(True);
for i:=High(Nodes) downto Low(Nodes) do begin
for j := Nodes[i].Index to High(DataGridResult.Rows)-1 do begin
// Move upper rows by one so the selected row gets overwritten
DataGridResult.Rows[j] := DataGridResult.Rows[j+1];
end;
end;
SetLength(DataGridResult.Rows, Length(DataGridResult.Rows) - Sender.SelectedCount);
Sender.DeleteSelectedNodes;
if not Assigned(FocusAfterDelete) then
FocusAfterDelete := Sender.GetLast;
if Assigned(FocusAfterDelete) then
SelectNode(Sender as TVirtualStringTree, FocusAfterDelete);
Sender.EndUpdate;
end else begin
// Should never get called as we block DELETEs on tables without a unique key
InvalidateVT(DataGrid, VTREE_NOTLOADED_PURGECACHE, False);
msg := 'Warning: Consistency problem detected.' + CRLF + CRLF
+ 'The last DELETE query affected ' + FormatNumber(Affected) + ' rows, when it should have touched '+FormatNumber(Sender.SelectedCount)+' row(s)!'
+ CRLF + CRLF
+ 'This is most likely caused by not having a primary key in the table''s definition.';
LogSQL( msg );
MessageDlg( msg, mtWarning, [mbOK], 0);
end;
DisplayRowCountStats;
end;
end;
{**
DataGrid: cancel INSERT or UPDATE mode, reset modified node data
}
procedure TMainForm.DataGridCancel(Sender: TObject);
var
i: Integer;
begin
case DataGridResult.Rows[DataGrid.FocusedNode.Index].State of
grsModified: GridFinalizeEditing(DataGrid);
grsInserted: begin
i := Length(DataGridResult.Rows);
DataGrid.DeleteNode(DataGrid.FocusedNode, False);
SetLength(DataGridResult.Rows, i-1);
// Focus+select last node if possible
actDataLastExecute(Sender);
end;
end;
DataGridHasChanges := False;
ValidateControls(Sender);
end;
procedure TMainForm.AnyGridKeyDown(Sender: TObject; var Key: Word; Shift:
TShiftState);
var
g: TVirtualStringTree;
begin
g := TVirtualStringTree(Sender);
case Key of
VK_HOME: g.FocusedColumn := 0;
VK_END: g.FocusedColumn := g.Header.Columns.Count-1;
VK_RETURN: if Assigned(g.FocusedNode) then g.EditNode(g.FocusedNode, g.FocusedColumn);
VK_DOWN: if (g = DataGrid) and Assigned(g.FocusedNode) and (g.FocusedNode.Index = g.RootNodeCount-1) then
actDataInsertExecute(Sender);
VK_NEXT: if (g = DataGrid) and Assigned(g.FocusedNode) and (g.FocusedNode.Index = g.RootNodeCount-1) then
actDataShowNext.Execute;
end;
end;
procedure TMainForm.DataGridEditing(Sender: TBaseVirtualTree; Node:
PVirtualNode; Column: TColumnIndex; var Allowed: Boolean);
begin
Allowed := DataGridEnsureFullRow(Sender as TVirtualStringTree, Node);
if Allowed then begin
// Move Esc shortcut from "Cancel row editing" to "Cancel cell editing"
actDataCancelChanges.ShortCut := 0;
actDataPostChanges.ShortCut := 0;
end;
end;
procedure TMainForm.DataGridEdited(Sender: TBaseVirtualTree; Node:
PVirtualNode; Column: TColumnIndex);
begin
// Reassign Esc to "Cancel row editing" action
if ([tsEditing, tsEditPending] * Sender.TreeStates) = [] then begin
actDataCancelChanges.ShortCut := TextToShortcut('Esc');
actDataPostChanges.ShortCut := TextToShortcut('Ctrl+Enter');
end;
end;
procedure TMainForm.DataGridEditCancelled(Sender: TBaseVirtualTree; Column:
TColumnIndex);
begin
// Reassign Esc to "Cancel row editing" action
actDataCancelChanges.ShortCut := TextToShortcut('Esc');
actDataPostChanges.ShortCut := TextToShortcut('Ctrl+Enter');
end;
procedure TMainForm.DataGridCreateEditor(Sender: TBaseVirtualTree; Node:
PVirtualNode; Column: TColumnIndex; out EditLink: IVTEditLink);
var
VT: TVirtualStringTree;
HexEditor: THexEditorLink;
DateTimeEditor: TDateTimeEditorLink;
EnumEditor: TEnumEditorLink;
SetEditor: TSetEditorLink;
InplaceEditor: TInplaceEditorLink;
TypeCat: TDatatypeCategoryIndex;
ForeignKey: TForeignKey;
TblColumn: TTableColumn;
idx: Integer;
KeyCol, TextCol, SQL, CreateTable: String;
Columns: TTableColumnList;
Keys: TTableKeyList;
ForeignKeys: TForeignKeyList;
ForeignResults: TMySQLQuery;
begin
VT := Sender as TVirtualStringTree;
// Find foreign key values on InnoDB table cells
for ForeignKey in SelectedTableForeignKeys do begin
idx := ForeignKey.Columns.IndexOf(DataGrid.Header.Columns[Column].Text);
if idx > -1 then begin
// Find the first text column if available and use that for displaying in the pulldown instead of using meaningless id numbers
CreateTable := Connection.GetVar('SHOW CREATE TABLE '+MaskMulti(ForeignKey.ReferenceTable), 1);
Columns := TTableColumnList.Create;
Keys := nil;
ForeignKeys := nil;
ParseTableStructure(CreateTable, Columns, Keys, ForeignKeys);
TextCol := '';
for TblColumn in Columns do begin
if (TblColumn.DataType.Category = dtcText) and (TblColumn.Name <> ForeignKey.ForeignColumns[idx]) then begin
TextCol := TblColumn.Name;
break;
end;
end;
KeyCol := Mask(ForeignKey.ForeignColumns[idx]);
SQL := 'SELECT '+KeyCol;
if TextCol <> '' then SQL := SQL + ', LEFT(' + Mask(TextCol) + ', 256)';
SQL := SQL + ' FROM '+MaskMulti(ForeignKey.ReferenceTable)+' GROUP BY '+KeyCol+' ORDER BY ';
if TextCol <> '' then SQL := SQL + Mask(TextCol) else SQL := SQL + KeyCol;
SQL := SQL + ' LIMIT 1000';
EnumEditor := TEnumEditorLink.Create(VT);
EnumEditor.DataType := DataGridResult.Columns[Column].Datatype;
EditLink := EnumEditor;
if TextCol = '' then
EnumEditor.ValueList := Connection.GetCol(SQL)
else begin
ForeignResults := Connection.GetResults(SQL);
while not ForeignResults.Eof do begin
EnumEditor.ValueList.Add(ForeignResults.Col(0));
EnumEditor.DisplayList.Add(ForeignResults.Col(0)+': '+ForeignResults.Col(1));
ForeignResults.Next;
end;
end;
break;
end;
end;
TypeCat := DataGridResult.Columns[Column].DatatypeCat;
if Assigned(EditLink) then
// Editor was created above, do nothing now
else if (TypeCat = dtcText) or ((TypeCat in [dtcBinary, dtcSpatial]) and actBlobAsText.Checked) then begin
InplaceEditor := TInplaceEditorLink.Create(VT);
InplaceEditor.DataType := DataGridResult.Columns[Column].Datatype;
InplaceEditor.MaxLength := DataGridResult.Columns[Column].MaxLength;
InplaceEditor.ButtonVisible := True;
EditLink := InplaceEditor;
end else if (TypeCat in [dtcBinary, dtcSpatial]) and prefEnableBinaryEditor then begin
HexEditor := THexEditorLink.Create(VT);
HexEditor.DataType := DataGridResult.Columns[Column].Datatype;
HexEditor.MaxLength := DataGridResult.Columns[Column].MaxLength;
EditLink := HexEditor;
end else if (TypeCat = dtcTemporal) and prefEnableDatetimeEditor then begin
DateTimeEditor := TDateTimeEditorLink.Create(VT);
DateTimeEditor.DataType := DataGridResult.Columns[Column].Datatype;
EditLink := DateTimeEditor;
end else if (TypeCat = dtcIntegerNamed) and prefEnableEnumEditor then begin
EnumEditor := TEnumEditorLink.Create(VT);
EnumEditor.DataType := DataGridResult.Columns[Column].Datatype;
EnumEditor.ValueList := DataGridResult.Columns[Column].ValueList;
EditLink := EnumEditor;
end else if (TypeCat = dtcSetNamed) and prefEnableSetEditor then begin
SetEditor := TSetEditorLink.Create(VT);
SetEditor.DataType := DataGridResult.Columns[Column].Datatype;
SetEditor.ValueList := DataGridResult.Columns[Column].ValueList;
EditLink := SetEditor;
end else begin
InplaceEditor := TInplaceEditorLink.Create(VT);
InplaceEditor.DataType := DataGridResult.Columns[Column].Datatype;
InplaceEditor.ButtonVisible := False;
EditLink := InplaceEditor;
end;
end;
procedure TMainForm.menuShowSizeColumnClick(Sender: TObject);
var
NewVal: Boolean;
begin
NewVal := not TMenuItem(Sender).Checked;
TMenuItem(Sender).Checked := newVal;
if NewVal then
DBtree.Header.Columns[1].Options := DBtree.Header.Columns[1].Options + [coVisible]
else
DBtree.Header.Columns[1].Options := DBtree.Header.Columns[1].Options - [coVisible];
OpenRegistry;
MainReg.WriteBool(REGNAME_SIZECOL_TREE, NewVal);
end;
procedure TMainForm.AutoCalcColWidth(Tree: TVirtualStringTree; Column: TColumnIndex);
var
Node: PVirtualNode;
i, ColTextWidth, ContentTextWidth: Integer;
Rect: TRect;
Col: TVirtualTreeColumn;
begin
// Find optimal default width for columns. Needs to be done late, after the SQL
// composing to enable text width calculation based on actual table content
// Weird: Fixes first time calculation always based on Tahoma/8pt font
Tree.Canvas.Font := Tree.Font;
Col := Tree.Header.Columns[Column];
if not (coVisible in Col.Options) then
Exit;
ColTextWidth := Tree.Canvas.TextWidth(Col.Text);
// Add space for sort glyph
if Col.ImageIndex > -1 then
ColTextWidth := ColTextWidth + 20;
Node := Tree.GetFirstVisible;
// Go backwards 50 nodes from focused one if tree was scrolled
i := 0;
if Assigned(Tree.FocusedNode) then begin
Node := Tree.FocusedNode;
while Assigned(Node) do begin
inc(i);
if (Node = Tree.GetFirst) or (i > 50) then
break;
Node := Tree.GetPreviousVisible(Node);
end;
end;
i := 0;
while Assigned(Node) do begin
// Note: this causes the node to load, an exception can propagate
// here if the query or connection dies.
Rect := Tree.GetDisplayRect(Node, Column, True, True);
ContentTextWidth := Rect.Right - Rect.Left;
if vsMultiLine in Node.States then
ContentTextWidth := Max(ContentTextWidth, Tree.Canvas.TextWidth(Tree.Text[Node, Column]));
ColTextWidth := Max(ColTextWidth, ContentTextWidth);
inc(i);
if i > 100 then break;
// GetDisplayRect may have implicitely taken the node away.
// Strange that Node keeps being assigned though, probably a timing issue.
if Tree.RootNodeCount = 0 then break;
Node := Tree.GetNextVisible(Node);
end;
// text margins and minimal extra space
ColTextWidth := ColTextWidth + Tree.TextMargin*2 + 5;
ColTextWidth := Min(ColTextWidth, prefMaxColWidth);
Col.Width := ColTextWidth;
end;
procedure TMainForm.AnyGridBeforeCellPaint(Sender: TBaseVirtualTree;
TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
CellPaintMode: TVTCellPaintMode; CellRect: TRect; var ContentRect: TRect);
var
gr: TGridResult;
cl: TColor;
begin
if Column = -1 then
Exit;
gr := GridResult(Sender);
cl := clNone;
if (vsSelected in Node.States) and (Node = Sender.FocusedNode) and (Column = Sender.FocusedColumn) then
cl := clHighlight
else if vsSelected in Node.States then
cl := $00DDDDDD
else if prefEnableNullBG and gr.Rows[Node.Index].Cells[Column].IsNull then
cl := prefNullBG;
if cl <> clNone then begin
TargetCanvas.Brush.Color := cl;
TargetCanvas.FillRect(CellRect);
end;
end;
procedure TMainForm.HandleDataGridAttributes(RefreshingData: Boolean);
var
rx: TRegExpr;
idx, i: Integer;
TestList: TStringList;
Sort, KeyName, FocusedCol, CellFocus, Filter: String;
begin
OpenRegistry;
MainReg.OpenKey(GetRegKeyTable, True);
actDataResetSorting.Enabled := False;
// Clear filter, column names and sort structure if gr
if not Assigned(DataGridHiddenColumns) then begin
DataGridHiddenColumns := TStringList.Create;
DataGridHiddenColumns.Delimiter := DELIM;
DataGridHiddenColumns.StrictDelimiter := True;
end;
if not Assigned(DataGridFocusedCell) then
DataGridFocusedCell := TStringList.Create;
// Remember focused node and column for selected table
if Assigned(DataGrid.FocusedNode) then begin
KeyName := Mask(DataGridDB)+'.'+Mask(DataGridTable);
FocusedCol := '';
if DataGrid.FocusedColumn <> NoColumn then
FocusedCol := DataGrid.Header.Columns[DataGrid.FocusedColumn].Text;
DataGridFocusedCell.Values[KeyName] := IntToStr(DataGrid.FocusedNode.Index) + DELIM + FocusedCol;
end;
DataGridFocusedNodeIndex := 0;
DataGridFocusedColumnName := '';
KeyName := Mask(SelectedTable.Database)+'.'+Mask(SelectedTable.Name);
CellFocus := DataGridFocusedCell.Values[KeyName];
if CellFocus <> '' then begin
DataGridFocusedNodeIndex := MakeInt(Explode(DELIM, CellFocus)[0]);
DataGridFocusedColumnName := Explode(DELIM, CellFocus)[1];
end;
if not RefreshingData then begin
DataGridHiddenColumns.Clear;
SynMemoFilter.Clear;
SetLength(DataGridSortColumns, 0);
DataGridWantedRowCount := 0;
while DataGridFocusedNodeIndex >= DataGridWantedRowCount do
Inc(DataGridWantedRowCount, prefGridRowcountStep);
end else begin
// Save current attributes if grid gets refreshed
if DataGridHiddenColumns.Count > 0 then
MainReg.WriteString(REGNAME_HIDDENCOLUMNS, DataGridHiddenColumns.DelimitedText)
else if MainReg.ValueExists(REGNAME_HIDDENCOLUMNS) then
MainReg.DeleteValue(REGNAME_HIDDENCOLUMNS);
if SynMemoFilter.GetTextLen > 0 then
MainReg.WriteString(REGNAME_FILTER, SynMemoFilter.Text)
else if MainReg.ValueExists(REGNAME_FILTER) then
MainReg.DeleteValue(REGNAME_FILTER);
for i := 0 to High(DataGridSortColumns) do
Sort := Sort + IntToStr(DataGridSortColumns[i].SortDirection) + '_' + DataGridSortColumns[i].ColumnName + DELIM;
if Sort <> '' then
MainReg.WriteString(REGNAME_SORT, Sort)
else if MainReg.ValueExists(REGNAME_SORT) then
MainReg.DeleteValue(REGNAME_SORT);
end;
// Auto remove registry spam if table folder is empty
TestList := TStringList.Create;
MainReg.GetValueNames(TestList);
if (not MainReg.HasSubKeys) and (TestList.Count = 0) then
MainReg.DeleteKey(GetRegKeyTable);
// Do nothing if table was not filtered yet
if not MainReg.OpenKey(GetRegKeyTable, False) then
Exit;
// Columns
if MainReg.ValueExists(REGNAME_HIDDENCOLUMNS) then
DataGridHiddenColumns.DelimitedText := MainReg.ReadString(REGNAME_HIDDENCOLUMNS);
// Set filter, without changing cursor position
if MainReg.ValueExists(REGNAME_FILTER) then begin
Filter := MainReg.ReadString(REGNAME_FILTER);
if SynMemoFilter.Text <> Filter then
SynMemoFilter.Text := Filter;
if SynMemoFilter.GetTextLen > 0 then
ToggleFilterPanel(True);
end;
// Sort
if MainReg.ValueExists(REGNAME_SORT) then begin
SetLength(DataGridSortColumns, 0);
rx := TRegExpr.Create;
rx.Expression := '\b(\d)_(.+)\'+DELIM;
rx.ModifierG := False;
if rx.Exec(MainReg.ReadString(REGNAME_SORT)) then while true do begin
idx := Length(DataGridSortColumns);
// Check if column exists, could be renamed or deleted
for i:=0 to SelectedTableColumns.Count-1 do begin
if SelectedTableColumns[i].Name = rx.Match[2] then begin
SetLength(DataGridSortColumns, idx+1);
DataGridSortColumns[idx] := TOrderCol.Create;
DataGridSortColumns[idx].ColumnName := rx.Match[2];
DataGridSortColumns[idx].SortDirection := StrToIntDef(rx.Match[1], ORDER_ASC);
break;
end;
end;
if not rx.ExecNext then
break;
end;
actDataResetSorting.Enabled := Length(DataGridSortColumns) > 0;
end;
end;
function TMainForm.GetRegKeyTable: String;
begin
// Return the slightly complex registry path to \Servers\ThisServer\curdb|curtable
Result := REGPATH + REGKEY_SESSIONS + SessionName + '\' +
ActiveDatabase + DELIM + SelectedTable.Name;
end;
procedure TMainForm.QueryGridFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex);
begin
ValidateControls(Sender);
end;
procedure TMainForm.pnlQueryHelpersCanResize(Sender: TObject; var NewWidth,
NewHeight: Integer; var Resize: Boolean);
begin
// Ensure minimum width for query helpers while resizing
Resize := NewWidth >= 20;
end;
procedure TMainForm.pnlQueryMemoCanResize(Sender: TObject; var NewWidth,
NewHeight: Integer; var Resize: Boolean);
begin
// Ensure visibility of query memo while resizing
Resize := NewWidth >= pnlQueryHelpers.Width + spltQueryHelpers.Width + 40;
end;
procedure TMainForm.DataGridMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var
Grid: TVirtualStringTree;
Hit: THitInfo;
begin
// Detect mouse hit in grid whitespace and apply changes.
Grid := Sender as TVirtualStringTree;
if not Assigned(Grid.FocusedNode) then
Exit;
Grid.GetHitTestInfoAt(X, Y, False, Hit);
if (Hit.HitNode = nil) or (Hit.HitColumn = NoColumn) or (Hit.HitColumn = InvalidColumn) then
DataGridPostUpdateOrInsert(Grid.FocusedNode);
end;
procedure TMainForm.ListDatabasesBeforeCellPaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas;
Node: PVirtualNode; Column: TColumnIndex; CellPaintMode: TVTCellPaintMode; CellRect: TRect;
var ContentRect: TRect);
var
vt: TVirtualStringTree;
Val, Max: Extended;
LoopNode: PVirtualNode;
begin
// Display color bars
if Column in [1,2,4..9] then begin
vt := Sender as TVirtualStringTree;
// Find out maximum value in column
LoopNode := vt.GetFirst;
Max := 1;
while Assigned(LoopNode) do begin
Val := MakeFloat(vt.Text[LoopNode, Column]);
if Val > Max then
Max := Val;
LoopNode := vt.GetNext(LoopNode);
end;
PaintColorBar(MakeFloat(vt.Text[Node, Column]), Max, TargetCanvas, CellRect);
end;
end;
procedure TMainForm.ListDatabasesBeforePaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas);
var
vt: TVirtualStringTree;
i: Integer;
begin
// Invalidate list of databases, before (re)painting
vt := Sender as TVirtualStringTree;
if vt.Tag = VTREE_LOADED then
Exit;
Screen.Cursor := crHourglass;
FreeAndNil(AllDatabasesDetails);
vt.Clear;
try
if Connection.InformationSchemaObjects.IndexOf('SCHEMATA') > -1 then
AllDatabasesDetails := Connection.GetResults('SELECT * FROM '+mask(DBNAME_INFORMATION_SCHEMA)+'.'+mask('SCHEMATA'));
except
on E:Exception do
LogSQL(E.Message, lcError);
end;
if vt.Tag = VTREE_NOTLOADED_PURGECACHE then begin
for i:=0 to AllDatabases.Count-1 do begin
if Connection.DbObjectsCached(AllDatabases[i]) then
Connection.GetDBObjects(AllDatabases[i], True);
end;
end;
vt.RootNodeCount := AllDatabases.Count;
tabDatabases.Caption := 'Databases ('+FormatNumber(vt.RootNodeCount)+')';
vt.Tag := VTREE_LOADED;
Screen.Cursor := crDefault;
end;
procedure TMainForm.ListDatabasesDblClick(Sender: TObject);
begin
// Select database on doubleclick
if Assigned(ListDatabases.FocusedNode) then try
ActiveDatabase := ListDatabases.Text[ListDatabases.FocusedNode, 0];
except
on E:Exception do LogSQL(E.Message, lcError);
end;
end;
procedure TMainForm.ListDatabasesGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode;
Kind: TVTImageKind; Column: TColumnIndex; var Ghosted: Boolean; var ImageIndex: Integer);
var
db: String;
begin
// Return icon index for databases. Ghosted if db objects not yet in cache.
if not (Kind in [ikNormal, ikSelected]) then
Exit;
if Column <> (Sender as TVirtualStringTree).Header.MainColumn then
Exit;
db := ListDatabases.Text[Node, 0];
if db = ActiveDatabase then
ImageIndex := ICONINDEX_DB_HIGHLIGHT
else
ImageIndex := ICONINDEX_DB;
Ghosted := not Connection.DbObjectsCached(db);
end;
procedure TMainForm.ListDatabasesGetNodeDataSize(Sender: TBaseVirtualTree;
var NodeDataSize: Integer);
begin
// Tell VirtualTree we're using a simple integer as data
NodeDataSize := SizeOf(Int64);
end;
procedure TMainForm.ListDatabasesInitNode(Sender: TBaseVirtualTree; ParentNode, Node: PVirtualNode;
var InitialStates: TVirtualNodeInitStates);
var
Idx: PInt;
begin
// Integers mapped to the node's index so nodes can be sorted without losing their database name
Idx := Sender.GetNodeData(Node);
Idx^ := Node.Index;
end;
procedure TMainForm.ListDatabasesGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
var
Idx: PInt;
Objects: TDBObjectList;
DBname: String;
function GetItemCount(ItemType: TListNodeType): String;
var
c: Integer;
o: TDBObject;
begin
if Objects <> nil then begin
c := 0;
for o in Objects do begin
if (ItemType = lntNone) or (o.NodeType = ItemType) then
Inc(c);
end;
Result := FormatNumber(c);
end else
Result := '';
end;
begin
// Return text for database columns
Idx := Sender.GetNodeData(Node);
DBname := AllDatabases[Idx^];
if Connection.DbObjectsCached(DBname) then
Objects := Connection.GetDBObjects(DBname);
case Column of
0: CellText := DBname;
1: if Assigned(Objects) then CellText := FormatByteNumber(Objects.DataSize)
else CellText := '';
2: CellText := GetItemCount(lntNone);
3: if Assigned(Objects) and (Objects.LastUpdate > 0) then CellText := DateTimeToStr(Objects.LastUpdate)
else CellText := '';
4: CellText := GetItemCount(lntTable);
5: CellText := GetItemCount(lntView);
6: CellText := GetItemCount(lntFunction);
7: CellText := GetItemCount(lntProcedure);
8: CellText := GetItemCount(lntTrigger);
9: CellText := GetItemCount(lntEvent);
10: begin
CellText := '';
if Assigned(AllDatabasesDetails) then begin
AllDatabasesDetails.First;
while not AllDatabasesDetails.Eof do begin
if AllDatabasesDetails.Col('SCHEMA_NAME', True) = DBname then begin
CellText := AllDatabasesDetails.Col('DEFAULT_COLLATION_NAME', True);
break;
end;
AllDatabasesDetails.Next;
end;
end;
end;
end;
end;
procedure TMainForm.ListVariablesBeforePaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas);
var
i : Integer;
vt: TVirtualStringTree;
Results: TMySQLQuery;
Sel: TStringList;
begin
// Display server variables
vt := Sender as TVirtualStringTree;
if vt.Tag = VTREE_LOADED then
Exit;
Sel := GetVTCaptions(vt, True);
DeInitializeVTNodes(vt);
Screen.Cursor := crHourglass;
try
Results := Connection.GetResults('SHOW VARIABLES');
SetLength(VTRowDataListVariables, Results.RecordCount);
for i:=0 to Results.RecordCount-1 do begin
VTRowDataListVariables[i].ImageIndex := 25;
VTRowDataListVariables[i].Captions := TStringList.Create;
VTRowDataListVariables[i].Captions.Add(Results.Col(0));
VTRowDataListVariables[i].Captions.Add(Results.Col(1));
Results.Next;
end;
FreeAndNil(Results);
vt.RootNodeCount := Length(VTRowDataListVariables);
vt.SortTree(vt.Header.SortColumn, vt.Header.SortDirection);
SetVTSelection(vt, Sel);
// Apply or reset filter
editFilterVTChange(Sender);
// Display number of listed values on tab
tabVariables.Caption := 'Variables (' + IntToStr(vt.RootNodeCount) + ')';
finally
// Important to flag the tree as "loaded", otherwise OnPaint will cause an endless loop
vt.Tag := VTREE_LOADED;
Screen.Cursor := crDefault;
end;
end;
procedure TMainForm.ListStatusBeforePaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas);
var
i: Integer;
valcount: Int64;
tmpval: Double;
Results: TMySQLQuery;
val, avg_perhour, avg_persec: String;
valIsBytes, valIsNumber: Boolean;
vt: TVirtualStringTree;
Sel: TStringList;
begin
// Display server status key/value pairs
vt := Sender as TVirtualStringTree;
if vt.Tag = VTREE_LOADED then
Exit;
Sel := GetVTCaptions(vt, True);
DeInitializeVTNodes(vt);
Screen.Cursor := crHourglass;
try
Results := Connection.GetResults('SHOW /*!50002 GLOBAL */ STATUS');
SetLength(VTRowDataListStatus, Results.RecordCount);
for i:=0 to Results.RecordCount-1 do begin
VTRowDataListStatus[i].ImageIndex := 25;
VTRowDataListStatus[i].Captions := TStringList.Create;
VTRowDataListStatus[i].Captions.Add(Results.Col(0));
val := Results.Col(1);
avg_perhour := '';
avg_persec := '';
// Detect value type
valIsNumber := IntToStr(MakeInt(val)) = val;
valIsBytes := valIsNumber and (Copy(Results.Col(0), 1, 6) = 'Bytes_');
// Calculate average values ...
if valIsNumber then begin
valCount := MakeInt(val);
// ... per hour
tmpval := valCount / ( Connection.ServerUptime / 60 / 60 );
if valIsBytes then avg_perhour := FormatByteNumber( Trunc(tmpval) )
else avg_perhour := FormatNumber( tmpval, 1 );
// ... per second
tmpval := valCount / Connection.ServerUptime;
if valIsBytes then avg_persec := FormatByteNumber( Trunc(tmpval) )
else avg_persec := FormatNumber( tmpval, 1 );
end;
// Format numeric or byte values
if valIsBytes then
val := FormatByteNumber(val)
else if valIsNumber then
val := FormatNumber(val);
VTRowDataListStatus[i].Captions.Add( val );
VTRowDataListStatus[i].Captions.Add(avg_perhour);
VTRowDataListStatus[i].Captions.Add(avg_persec);
Results.Next;
end;
FreeAndNil(Results);
// Tell VirtualTree the number of nodes it will display
vt.RootNodeCount := Length(VTRowDataListStatus);
vt.SortTree(vt.Header.SortColumn, vt.Header.SortDirection);
SetVTSelection(vt, Sel);
// Apply or reset filter
editFilterVTChange(Sender);
// Display number of listed values on tab
tabStatus.Caption := 'Status (' + IntToStr(vt.RootNodeCount) + ')';
finally
vt.Tag := VTREE_LOADED;
Screen.Cursor := crDefault;
end;
end;
procedure TMainForm.ListProcessesBeforePaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas);
var
i, j: Integer;
Results: TMySQLQuery;
vt: TVirtualStringTree;
Sel: TStringList;
Text: String;
const
InfoLen = SIZE_KB*50;
begin
// Display client threads
vt := Sender as TVirtualStringTree;
if vt.Tag = VTREE_LOADED then
Exit;
vt.OnFocusChanged(vt, vt.FocusedNode, vt.FocusedColumn);
Sel := GetVTCaptions(vt, True);
DeInitializeVTNodes(vt);
Screen.Cursor := crHourglass;
try
if Connection.InformationSchemaObjects.IndexOf('PROCESSLIST') > -1 then begin
// Minimize network traffic on newer servers by fetching only first KB of SQL query in "Info" column
Results := Connection.GetResults('SELECT '+mask('ID')+', '+mask('USER')+', '+mask('HOST')+', '+mask('DB')+', '
+ mask('COMMAND')+', '+mask('TIME')+', '+mask('STATE')+', LEFT('+mask('INFO')+', '+IntToStr(InfoLen)+') AS '+mask('Info')
+ ' FROM '+mask(DBNAME_INFORMATION_SCHEMA)+'.'+mask('PROCESSLIST'));
end else begin
// Older servers fetch the whole query length, but at least we cut them off below, so a high memory usage is just a peak
Results := Connection.GetResults('SHOW FULL PROCESSLIST');
end;
SetLength(VTRowDataListProcesses, Results.RecordCount);
ProcessListMaxTime := 1;
for i:=0 to Results.RecordCount-1 do begin
if AnsiCompareText(Results.Col(4), 'Killed') = 0 then
VTRowDataListProcesses[i].ImageIndex := 26 // killed
else begin
if Results.Col('Info') = '' then
VTRowDataListProcesses[i].ImageIndex := 55 // idle
else
VTRowDataListProcesses[i].ImageIndex := 57 // running query
end;
VTRowDataListProcesses[i].Captions := TStringList.Create;
for j:=0 to Results.ColumnCount-1 do begin
Text := Results.Col(j);
if Results.ColumnNames[j] = 'Info' then
Text := sstr(Text, InfoLen);
VTRowDataListProcesses[i].Captions.Add(Text);
end;
ProcessListMaxTime := Max(ProcessListMaxTime, MakeInt(Results.Col(5)));
Results.Next;
end;
FreeAndNil(Results);
vt.RootNodeCount := Length(VTRowDataListProcesses);
vt.SortTree(vt.Header.SortColumn, vt.Header.SortDirection);
// Reset focused node and column, so OnFocusChange will fire, and update the SQL viewer
vt.FocusedNode := nil;
vt.FocusedColumn := NoColumn;
SetVTSelection(vt, Sel);
// Apply or reset filter
editFilterVTChange(Sender);
// Display number of listed values on tab
tabProcessList.Caption := 'Process-List (' + IntToStr(vt.RootNodeCount) + ')';
except
on E: Exception do begin
LogSQL('Error loading process list (automatic refresh disabled): ' + e.Message);
TimerRefresh.Enabled := false;
end;
end;
vt.Tag := VTREE_LOADED;
Screen.Cursor := crDefault;
end;
procedure TMainForm.ListCommandStatsBeforePaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas);
procedure addLVitem( idx: Integer; caption: String; commandCount: Int64; totalCount: Int64 );
var
tmpval : Double;
begin
VTRowDataListCommandStats[idx].ImageIndex := 25;
VTRowDataListCommandStats[idx].Captions := TStringList.Create;
caption := Copy( caption, 5, Length(caption) );
caption := StringReplace( caption, '_', ' ', [rfReplaceAll] );
VTRowDataListCommandStats[idx].Captions.Add( caption );
// Total Frequency
VTRowDataListCommandStats[idx].Captions.Add( FormatNumber( commandCount ) );
// Average per hour
tmpval := commandCount / ( Connection.ServerUptime / 60 / 60 );
VTRowDataListCommandStats[idx].Captions.Add( FormatNumber( tmpval, 1 ) );
// Average per second
tmpval := commandCount / Connection.ServerUptime;
VTRowDataListCommandStats[idx].Captions.Add( FormatNumber( tmpval, 1 ) );
// Percentage. Take care of division by zero errors and Int64's
if commandCount < 1 then
commandCount := 1;
if totalCount < 1 then
totalCount := 1;
tmpval := 100 / totalCount * commandCount;
VTRowDataListCommandStats[idx].Captions.Add( FormatNumber( tmpval, 1 ) + ' %' );
end;
var
i: Integer;
questions: Int64;
Results: TMySQLQuery;
vt: TVirtualStringTree;
Sel: TStringList;
begin
// Display command statistics
vt := Sender as TVirtualStringTree;
if vt.Tag = VTREE_LOADED then
Exit;
Sel := GetVTCaptions(vt, True);
DeInitializeVTNodes(vt);
Screen.Cursor := crHourglass;
try
Results := Connection.GetResults('SHOW /*!50002 GLOBAL */ STATUS LIKE ''Com\_%''' );
questions := 0;
while not Results.Eof do begin
Inc(questions, MakeInt(Results.Col(1)));
Results.Next;
end;
SetLength(VTRowDataListCommandStats, Results.RecordCount+1);
addLVitem(0, ' All commands', questions, questions );
Results.First;
for i:=1 to Results.RecordCount do begin
addLVitem(i, Results.Col(0), MakeInt(Results.Col(1)), questions );
Results.Next;
end;
FreeAndNil(Results);
// Tell VirtualTree the number of nodes it will display
vt.RootNodeCount := Length(VTRowDataListCommandStats);
vt.SortTree(vt.Header.SortColumn, vt.Header.SortDirection);
SetVTSelection(vt, Sel);
// Apply or reset filter
editFilterVTChange(Sender);
// Display number of listed values on tab
tabCommandStats.Caption := 'Command-Statistics (' + IntToStr(vt.RootNodeCount) + ')';
finally
vt.Tag := VTREE_LOADED;
Screen.Cursor := crDefault;
end;
end;
procedure TMainForm.actCopyOrCutExecute(Sender: TObject);
var
Control: TWinControl;
Edit: TCustomEdit;
Combo: TCustomComboBox;
Grid: TVirtualStringTree;
SynMemo: TSynMemo;
Success, DoCut: Boolean;
SQLStream: TMemoryStream;
begin
// Copy text from a focused control to clipboard
Success := False;
Control := Screen.ActiveControl;
DoCut := Sender = actCut;
if Control is TCustomEdit then begin
Edit := TCustomEdit(Control);
if Edit.SelLength > 0 then begin
if DoCut then Edit.CutToClipboard
else Edit.CopyToClipboard;
Success := True;
end;
end else if Control is TCustomComboBox then begin
Combo := TCustomComboBox(Control);
if Combo.SelLength > 0 then begin
Clipboard.AsText := Combo.SelText;
if DoCut then Combo.SelText := '';
Success := True;
end;
end else if Control is TVirtualStringTree then begin
Grid := Control as TVirtualStringTree;
if Assigned(Grid.FocusedNode) then begin
DataGridEnsureFullRow(Grid, Grid.FocusedNode);
Clipboard.AsText := Grid.Text[Grid.FocusedNode, Grid.FocusedColumn];
if (Grid = ActiveGrid) and DoCut then
Grid.Text[Grid.FocusedNode, Grid.FocusedColumn] := '';
Success := True;
end;
end else if Control is TSynMemo then begin
SynMemo := Control as TSynMemo;
if SynMemo.SelAvail then begin
// Create both text and HTML clipboard format, so rich text applications can paste highlighted SQL
SynExporterHTML1.ExportAll(Explode(CRLF, SynMemo.SelText));
if DoCut then SynMemo.CutToClipboard
else SynMemo.CopyToClipboard;
SQLStream := TMemoryStream.Create;
SynExporterHTML1.SaveToStream(SQLStream);
StreamToClipboard(nil, SQLStream, False);
Success := True;
end;
end;
if not Success then
MessageBeep(MB_ICONASTERISK);
end;
procedure TMainForm.actPasteExecute(Sender: TObject);
var
Control: TWinControl;
Edit: TCustomEdit;
Combo: TComboBox;
Grid: TVirtualStringTree;
SynMemo: TSynMemo;
Success: Boolean;
begin
// Paste text into the focused control
Success := False;
Control := Screen.ActiveControl;
if not Clipboard.HasFormat(CF_TEXT) then begin
// Do nothing, we cannot paste a picture or so
end else if Control is TCustomEdit then begin
Edit := TCustomEdit(Control);
if not Edit.ReadOnly then begin
Edit.PasteFromClipboard;
Success := True;
end;
end else if Control is TComboBox then begin
Combo := TComboBox(Control);
if Combo.Style = csDropDown then begin
Combo.SelText := ClipBoard.AsText;
Success := True;
end;
end else if Control is TVirtualStringTree then begin
Grid := Control as TVirtualStringTree;
if Assigned(Grid.FocusedNode) and (Grid = ActiveGrid) then begin
Grid.Text[Grid.FocusedNode, Grid.FocusedColumn] := ClipBoard.AsText;
Success := True;
end;
end else if Control is TSynMemo then begin
SynMemo := TSynMemo(Control);
if not SynMemo.ReadOnly then begin
SynMemo.PasteFromClipboard;
Success := True;
end;
end;
if not Success then
MessageBeep(MB_ICONASTERISK);
end;
procedure TMainForm.actSelectAllExecute(Sender: TObject);
var
Control: TWinControl;
Grid: TVirtualStringTree;
ListBox: TListBox;
Success: Boolean;
begin
// Select all items, text or whatever
Success := False;
Control := Screen.ActiveControl;
if Control is TCustomEdit then begin
TCustomEdit(Control).SelectAll;
Success := True;
end else if Control is TVirtualStringTree then begin
Grid := TVirtualStringTree(Control);
if toMultiSelect in Grid.TreeOptions.SelectionOptions then begin
Grid.SelectAll(False);
Success := True;
end;
end else if Control is TSynMemo then begin
TSynMemo(Control).SelectAll;
Success := True;
end else if Control is TListBox then begin
ListBox := TListBox(Control);
if ListBox.MultiSelect then begin
ListBox.SelectAll;
Success := True;
end;
end;
if not Success then
MessageBeep(MB_ICONASTERISK);
end;
procedure TMainForm.actSelectInverseExecute(Sender: TObject);
var
Control: TWinControl;
Grid: TVirtualStringTree;
ListBox: TListBox;
Success: Boolean;
i: Integer;
begin
// Invert selection in grids or listboxes
Success := False;
Control := Screen.ActiveControl;
if Control is TVirtualStringTree then begin
Grid := TVirtualStringTree(Control);
if toMultiSelect in Grid.TreeOptions.SelectionOptions then begin
Grid.InvertSelection(False);
Success := True;
end;
end else if Control is TListBox then begin
ListBox := TListBox(Control);
if ListBox.MultiSelect then begin
for i:=0 to ListBox.Count-1 do
ListBox.Selected[i] := not ListBox.Selected[i];
Success := True;
end;
end;
if not Success then
MessageBeep(MB_ICONASTERISK);
end;
procedure TMainForm.EnumerateRecentFilters;
var
flt: TStringList;
i: Integer;
item: TMenuItem;
rx: TRegExpr;
capt: String;
begin
// Reset menu and combobox
menuRecentFilters.Enabled := False;
for i := menuRecentFilters.Count - 1 downto 0 do
menuRecentFilters.Delete(i);
comboRecentFilters.Items.Clear;
// Enumerate recent filters from registry
if MainReg.OpenKey(GetRegKeyTable+'\'+REGNAME_FILTERS, False) then begin
flt := TStringList.Create;
rx := TRegExpr.Create;
rx.Expression := '\s+';
MainReg.GetValueNames(flt);
for i := 0 to flt.Count - 1 do begin
item := TMenuItem.Create(popupFilter);
capt := MainReg.ReadString(flt[i]);
capt := rx.Replace(capt, ' ', True);
item.Hint := capt;
item.Caption := sstr(capt, 50);
item.Tag := MakeInt(flt[i]);
item.OnClick := LoadRecentFilter;
menuRecentFilters.Add(item);
comboRecentFilters.Items.Add(sstr(capt, 100));
end;
FreeAndNil(rx);
FreeAndNil(flt);
menuRecentFilters.Enabled := menuRecentFilters.Count > 0;
end;
comboRecentFilters.Visible := comboRecentFilters.Items.Count > 0;
lblRecentFilters.Visible := comboRecentFilters.Visible;
SynMemoFilter.Height := pnlFilter.Height - 3;
SynMemoFilter.Top := comboRecentFilters.Top;
if comboRecentFilters.Visible then begin
SynMemoFilter.Height := SynMemoFilter.Height - comboRecentFilters.Height;
SynMemoFilter.Top := SynMemoFilter.Top + comboRecentFilters.Height;
comboRecentFilters.ItemIndex := 0;
end;
end;
procedure TMainForm.LoadRecentFilter(Sender: TObject);
var
key: Integer;
begin
// Event handler for both dynamic popup menu items and filter combobox
if Sender is TMenuItem then
key := (Sender as TMenuItem).Tag
else
key := (Sender as TComboBox).ItemIndex+1;
if MainReg.OpenKey(GetRegKeyTable+'\'+REGNAME_FILTERS, False) then begin
SynMemoFilter.UndoList.AddGroupBreak;
SynMemoFilter.BeginUpdate;
SynMemoFilter.SelectAll;
SynMemoFilter.SelText := MainReg.ReadString(IntToStr(key));
SynMemoFilter.EndUpdate;
end;
end;
procedure TMainForm.PlaceObjectEditor(Obj: TDBObject);
var
EditorClass: TDBObjectEditorClass;
begin
// Place the relevant editor frame onto the editor tab, hide all others
if Assigned(ActiveObjectEditor) and (Obj.NodeType <> ActiveObjectEditor.DBObject.NodeType) then
FreeAndNil(ActiveObjectEditor);
case Obj.NodeType of
lntTable: EditorClass := TfrmTableEditor;
lntView: EditorClass := TfrmView;
lntProcedure, lntFunction: EditorClass := TfrmRoutineEditor;
lntTrigger: EditorClass := TfrmTriggerEditor;
lntEvent: EditorClass := TfrmEventEditor;
else Exit;
end;
if not Assigned(ActiveObjectEditor) then begin
ActiveObjectEditor := EditorClass.Create(tabEditor);
ActiveObjectEditor.Parent := tabEditor;
end;
ActiveObjectEditor.Init(Obj);
end;
procedure TMainForm.UpdateEditorTab;
var
Cap: String;
begin
tabEditor.ImageIndex := ActiveObjectEditor.DBObject.ImageIndex;
Cap := ActiveObjectEditor.DBObject.ObjType+': ';
if ActiveObjectEditor.DBObject.Name = '' then
Cap := Cap + '[Untitled]'
else
Cap := sstr(Cap + ActiveObjectEditor.DBObject.Name, 30);
SetTabCaption(tabEditor.PageIndex, Cap);
end;
procedure TMainForm.actEditObjectExecute(Sender: TObject);
var
Obj: PDBObject;
begin
debug('actEditObjectExecute()');
if ListTables.Focused then begin
// Got here from ListTables.OnDblClick or ListTables's context menu item "Edit"
Obj := ListTables.GetNodeData(ListTables.FocusedNode);
if (Obj.Name <> SelectedTable.Name) or (Obj.NodeType <> SelectedTable.NodeType) then
SelectDBObject(Obj.Name, Obj.NodeType);
end;
case DBtree.GetNodeLevel(DBtree.FocusedNode) of
1: begin
if CreateDatabaseForm = nil then
CreateDatabaseForm := TCreateDatabaseForm.Create(Self);
CreateDatabaseForm.modifyDB := ActiveDatabase;
if CreateDatabaseForm.ShowModal = mrOk then
InvalidateVT(DBtree, VTREE_NOTLOADED_PURGECACHE, True);
end;
2: PlaceObjectEditor(SelectedTable);
end;
end;
procedure TMainForm.ListTablesKeyPress(Sender: TObject; var Key: Char);
begin
// Open object editor on pressing Enter
if Ord(Key) = VK_RETURN then
ListTables.OnDblClick(Sender);
end;
procedure TMainForm.ListTablesDblClick(Sender: TObject);
var
Obj: PDBObject;
vt: TVirtualStringTree;
begin
// DoubleClick: Display editor
debug('ListTablesDblClick()');
vt := Sender as TVirtualStringTree;
if Assigned(vt.FocusedNode) then begin
Obj := vt.GetNodeData(vt.FocusedNode);
SelectDBObject(vt.Text[vt.FocusedNode, vt.FocusedColumn], Obj.NodeType);
PageControlMainChange(Sender);
end;
end;
procedure TMainForm.actNewQueryTabExecute(Sender: TObject);
var
i: Integer;
QueryTab: TQueryTab;
begin
i := QueryTabs[QueryTabs.Count-1].Number + 1;
QueryTabs.Add(TQueryTab.Create);
QueryTab := QueryTabs[QueryTabs.Count-1];
QueryTab.Number := i;
QueryTab.GridResult := TGridResult.Create;
QueryTab.TabSheet := TTabSheet.Create(PageControlMain);
QueryTab.TabSheet.PageControl := PageControlMain;
QueryTab.TabSheet.ImageIndex := tabQuery.ImageIndex;
QueryTab.CloseButton := TSpeedButton.Create(QueryTab.TabSheet);
QueryTab.CloseButton.Parent := PageControlMain;
QueryTab.CloseButton.Width := 16;
QueryTab.CloseButton.Height := 16;
QueryTab.CloseButton.Flat := True;
ImageListMain.GetBitmap(134, QueryTab.CloseButton.Glyph);
QueryTab.CloseButton.OnMouseDown := CloseButtonOnMouseDown;
QueryTab.CloseButton.OnMouseUp := CloseButtonOnMouseUp;
SetTabCaption(QueryTab.TabSheet.PageIndex, '');
// Dumb code which replicates all controls from tabQuery
QueryTab.pnlMemo := TPanel.Create(QueryTab.TabSheet);
QueryTab.pnlMemo.Parent := QueryTab.TabSheet;
QueryTab.pnlMemo.Tag := pnlQueryMemo.Tag;
QueryTab.pnlMemo.BevelOuter := pnlQueryMemo.BevelOuter;
QueryTab.pnlMemo.Align := pnlQueryMemo.Align;
QueryTab.Memo := TSynMemo.Create(QueryTab.pnlMemo);
QueryTab.Memo.Parent := QueryTab.pnlMemo;
QueryTab.Memo.Tag := SynMemoQuery.Tag;
QueryTab.Memo.Align := SynMemoQuery.Align;
QueryTab.Memo.Options := SynMemoQuery.Options;
QueryTab.Memo.PopupMenu := SynMemoQuery.PopupMenu;
QueryTab.Memo.TabWidth := SynMemoQuery.TabWidth;
QueryTab.Memo.RightEdge := SynMemoQuery.RightEdge;
QueryTab.Memo.WantTabs := SynMemoQuery.WantTabs;
QueryTab.Memo.Highlighter := SynMemoQuery.Highlighter;
QueryTab.Memo.Gutter.Assign(SynMemoQuery.Gutter);
QueryTab.Memo.Font.Assign(SynMemoQuery.Font);
QueryTab.Memo.ActiveLineColor := SynMemoQuery.ActiveLineColor;
QueryTab.Memo.OnDragDrop := SynMemoQuery.OnDragDrop;
QueryTab.Memo.OnDragOver := SynMemoQuery.OnDragOver;
QueryTab.Memo.OnDropFiles := SynMemoQuery.OnDropFiles;
QueryTab.Memo.OnReplaceText := SynMemoQuery.OnReplaceText;
QueryTab.Memo.OnStatusChange := SynMemoQuery.OnStatusChange;
QueryTab.Memo.OnPaintTransient := SynMemoQuery.OnPaintTransient;
SynCompletionProposal.AddEditor(QueryTab.Memo);
QueryTab.spltHelpers := TSplitter.Create(QueryTab.pnlMemo);
QueryTab.spltHelpers.Parent := QueryTab.pnlMemo;
QueryTab.spltHelpers.Tag := spltQueryHelpers.Tag;
QueryTab.spltHelpers.Align := spltQueryHelpers.Align;
QueryTab.spltHelpers.Cursor := spltQueryHelpers.Cursor;
QueryTab.spltHelpers.ResizeStyle := spltQueryHelpers.ResizeStyle;
QueryTab.spltHelpers.Width := spltQueryHelpers.Width;
QueryTab.pnlHelpers := TPanel.Create(QueryTab.pnlMemo);
QueryTab.pnlHelpers.Parent := QueryTab.pnlMemo;
QueryTab.pnlHelpers.Tag := pnlQueryHelpers.Tag;
QueryTab.pnlHelpers.BevelOuter := pnlQueryHelpers.BevelOuter;
QueryTab.pnlHelpers.Align := pnlQueryHelpers.Align;
QueryTab.lboxHelpers := TListBox.Create(QueryTab.pnlHelpers);
QueryTab.lboxHelpers.Parent := QueryTab.pnlHelpers;
QueryTab.lboxHelpers.Tag := lboxQueryHelpers.Tag;
QueryTab.lboxHelpers.Align := lboxQueryHelpers.Align;
QueryTab.lboxHelpers.PopupMenu := lboxQueryHelpers.PopupMenu;
QueryTab.lboxHelpers.MultiSelect := lboxQueryHelpers.MultiSelect;
QueryTab.lboxHelpers.DragMode := lboxQueryHelpers.DragMode;
QueryTab.lboxHelpers.Font.Assign(lboxQueryHelpers.Font);
QueryTab.lboxHelpers.OnClick := lboxQueryHelpers.OnClick;
QueryTab.lboxHelpers.OnDblClick := lboxQueryHelpers.OnDblClick;
QueryTab.tabsetHelpers := TTabSet.Create(QueryTab.pnlHelpers);
QueryTab.tabsetHelpers.Parent := QueryTab.pnlHelpers;
QueryTab.tabsetHelpers.Tag := tabsetQueryHelpers.Tag;
QueryTab.tabsetHelpers.Height := tabsetQueryHelpers.Height;
QueryTab.tabsetHelpers.Align := tabsetQueryHelpers.Align;
QueryTab.tabsetHelpers.Tabs := tabsetQueryHelpers.Tabs;
QueryTab.tabsetHelpers.Style := tabsetQueryHelpers.Style;
QueryTab.tabsetHelpers.Font.Assign(tabsetQueryHelpers.Font);
QueryTab.tabsetHelpers.OnChange := tabsetQueryHelpers.OnChange;
QueryTab.spltQuery := TSplitter.Create(QueryTab.TabSheet);
QueryTab.spltQuery.Parent := QueryTab.TabSheet;
QueryTab.spltQuery.Tag := spltQuery.Tag;
QueryTab.spltQuery.Align := spltQuery.Align;
QueryTab.spltQuery.Height := spltQuery.Height;
QueryTab.spltQuery.Cursor := spltQuery.Cursor;
QueryTab.spltQuery.ResizeStyle := spltQuery.ResizeStyle;
QueryTab.spltQuery.AutoSnap := spltQuery.AutoSnap;
QueryTab.LabelResultInfo := TLabel.Create(QueryTab.TabSheet);
QueryTab.LabelResultInfo.Parent := QueryTab.TabSheet;
QueryTab.LabelResultInfo.Tag := LabelResultInfo.Tag;
QueryTab.LabelResultInfo.Align := LabelResultInfo.Align;
QueryTab.LabelResultInfo.Font.Assign(LabelResultInfo.Font);
QueryTab.LabelResultInfo.Caption := '';
QueryTab.Grid := TVirtualStringTree.Create(QueryTab.TabSheet);
QueryTab.Grid.Parent := QueryTab.TabSheet;
QueryTab.Grid.Tag := QueryGrid.Tag;
QueryTab.Grid.Align := QueryGrid.Align;
QueryTab.Grid.TreeOptions := QueryGrid.TreeOptions;
QueryTab.Grid.PopupMenu := QueryGrid.PopupMenu;
QueryTab.Grid.LineStyle := QueryGrid.LineStyle;
QueryTab.Grid.Font.Assign(QueryGrid.Font);
QueryTab.Grid.Header.ParentFont := QueryGrid.Header.ParentFont;
QueryTab.Grid.WantTabs := QueryGrid.WantTabs;
QueryTab.Grid.AutoScrollDelay := QueryGrid.AutoScrollDelay;
QueryTab.Grid.OnBeforeCellPaint := QueryGrid.OnBeforeCellPaint;
QueryTab.Grid.OnFocusChanged := QueryGrid.OnFocusChanged;
QueryTab.Grid.OnGetText := QueryGrid.OnGetText;
QueryTab.Grid.OnInitNode := QueryGrid.OnInitNode;
QueryTab.Grid.OnKeyDown := QueryGrid.OnKeyDown;
QueryTab.Grid.OnPaintText := QueryGrid.OnPaintText;
FixVT(QueryTab.Grid, prefGridRowsLineCount);
SetupSynEditors;
// Set splitter positions
QueryTab.pnlMemo.Height := pnlQueryMemo.Height;
QueryTab.pnlMemo.Top := pnlQueryMemo.Top;
QueryTab.spltQuery.Top := spltQuery.Top;
QueryTab.pnlHelpers.Width := pnlQueryHelpers.Width;
// Show new tab
PageControlMain.ActivePage := QueryTab.TabSheet;
PageControlMainChange(Sender);
end;
procedure TMainForm.panelTopDblClick(Sender: TObject);
var
aRect: TRect;
aPoint: TPoint;
begin
// Catch doubleclick on PageControlMain's underlying panel, which gets fired
// when user clicks right besides the visible tabs
aPoint := PageControlMain.ClientOrigin;
aRect := Rect(aPoint.X, aPoint.Y, aPoint.X + PageControlMain.Width, aPoint.Y + PageControlMain.Height - tabQuery.Height);
GetCursorPos(aPoint);
if PtInRect(aRect, aPoint) then
actNewQueryTab.Execute;
end;
procedure TMainForm.actCloseQueryTabExecute(Sender: TObject);
begin
// Close active query tab by main action
CloseQueryTab(PageControlMain.ActivePageIndex);
end;
procedure TMainForm.menuCloseQueryTab(Sender: TObject);
var
aPoint: TPoint;
begin
// Close query tab by menu item
aPoint := PageControlMain.ScreenToClient(popupMainTabs.PopupPoint);
CloseQueryTab(GetMainTabAt(aPoint.X, aPoint.Y));
end;
procedure TMainForm.popupMainTabsPopup(Sender: TObject);
var
aPoint: TPoint;
PageIndex: Integer;
begin
// Detect if there is a tab under mouse position
aPoint := PageControlMain.ScreenToClient(popupMainTabs.PopupPoint);
PageIndex := GetMainTabAt(aPoint.X, aPoint.Y);
menuCloseTab.Enabled := IsQueryTab(PageIndex, False);
end;
procedure TMainForm.CloseQueryTab(PageIndex: Integer);
var
NewPageIndex: Integer;
begin
if not IsQueryTab(PageIndex, False) then
Exit;
// Ask user if query content shall be saved to disk
if not ConfirmTabClose(PageIndex) then
Exit;
// Work around bugs in ComCtrls.TPageControl.RemovePage
NewPageIndex := PageControlMain.ActivePageIndex;
if NewPageIndex >= PageIndex then
Dec(NewPageIndex);
PageControlMain.Pages[PageIndex].Free;
QueryTabs.Delete(PageIndex-tabQuery.PageIndex);
PageControlMain.ActivePageIndex := NewPageIndex;
FixQueryTabCloseButtons;
PageControlMain.OnChange(PageControlMain);
end;
procedure TMainForm.CloseButtonOnMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
FLastMouseDownCloseButton := Sender;
end;
procedure TMainForm.CloseButtonOnMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
aPoint: TPoint;
begin
// Click on "Close" button of Query tab
if Button <> mbLeft then
Exit;
// Between MousDown and MouseUp it is possible that the focused tab has switched. As we simulate a mouse-click
// here, we must check if also the MouseDown event was fired on this particular button. See issue #1469.
if (Sender <> FLastMouseDownCloseButton) then
Exit;
aPoint := PageControlMain.ScreenToClient((Sender as TSpeedButton).ClientToScreen(Point(X,Y)));
CloseQueryTab(GetMainTabAt(aPoint.X, aPoint.Y));
end;
procedure TMainForm.PageControlMainMouseUp(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
CurTickcount: Cardinal;
TabNumber: Integer;
begin
// Simulate doubleclick on tab to close it
CurTickcount := GetTickCount;
TabNumber := GetMainTabAt(X, Y);
if (TabNumber = FLastTabNumberOnMouseUp)
and (CurTickcount - FLastMouseUpOnPageControl <= GetDoubleClickTime) then
CloseQueryTab(TabNumber)
else begin
FLastMouseUpOnPageControl := CurTickcount;
FLastTabNumberOnMouseUp := TabNumber;
end;
end;
function TMainForm.GetMainTabAt(X, Y: Integer): Integer;
var
i: Integer;
begin
// Return page index of main tab by coordinates
Result := PageControlMain.IndexOfTabAt(X, Y);
for i:=0 to PageControlMain.PageCount-1 do begin
if (i<=Result) and (not PageControlMain.Pages[i].TabVisible) then
Inc(Result);
end;
end;
procedure TMainForm.FixQueryTabCloseButtons;
var
i, PageIndex, VisiblePageIndex: Integer;
Rect: TRect;
btn: TSpeedButton;
begin
// Fix positions of "Close" buttons on Query tabs
// Avoid AV on Startup, when Mainform.OnResize is called once or twice implicitely.
if not Assigned(btnAddTab) then
Exit;
for PageIndex:=tabQuery.PageIndex+1 to PageControlMain.PageCount-1 do begin
VisiblePageIndex := PageIndex;
for i:=0 to PageControlMain.PageCount-1 do begin
if (i<=VisiblePageIndex) and (not PageControlMain.Pages[i].TabVisible) then
Dec(VisiblePageIndex);
end;
Rect := PageControlMain.TabRect(VisiblePageIndex);
btn := QueryTabs[PageIndex-tabQuery.PageIndex].CloseButton;
btn.Top := Rect.Top + 2;
btn.Left := Rect.Right - 19;
end;
// Set position of "Add tab" button
VisiblePageIndex := PageControlMain.PageCount-1;
for i:=0 to PageControlMain.PageCount-1 do begin
if not PageControlMain.Pages[i].TabVisible then
Dec(VisiblePageIndex);
end;
Rect := PageControlMain.TabRect(VisiblePageIndex);
btnAddTab.Top := Rect.Top;
btnAddTab.Left := Rect.Right + 5;
end;
function TMainForm.ActiveQueryTab: TQueryTab;
var
idx: Integer;
begin
idx := PageControlMain.ActivePageIndex-tabQuery.PageIndex;
if (idx >= 0) and (idx < QueryTabs.Count) then
Result := QueryTabs[idx]
else
Result := nil;
end;
function TMainForm.ActiveQueryMemo: TSynMemo;
begin
// Return current query memo
Result := ActiveQueryTab.Memo;
end;
function TMainForm.ActiveQueryHelpers: TListBox;
begin
// Return current query helpers listbox
Result := ActiveQueryTab.lboxHelpers;
end;
function TMainForm.ActiveQueryTabset: TTabset;
begin
// Return current query helpers tabset
Result := ActiveQueryTab.tabsetHelpers
end;
function TMainForm.ActiveGrid: TVirtualStringTree;
begin
Result := nil;
if PageControlMain.ActivePage = tabData then Result := DataGrid
else if ActiveQueryTab <> nil then
Result := ActiveQueryTab.Grid;
end;
function TMainForm.GridResult(Grid: TBaseVirtualTree): TGridResult;
begin
// All grids (data- and query-grids) are placed directly on a TTabSheet
Result := GridResult((Grid.Parent as TTabSheet).PageIndex)
end;
function TMainForm.GridResult(PageIndex: Integer): TGridResult;
begin
// Return the grid result for "Data" or one of the "Query" tabs.
// Results are enumerated like the tabs on which they get displayed, starting at tabData
Dec(PageIndex, tabQuery.PageIndex);
if PageIndex < 0 then
Result := DataGridResult
else if PageIndex < QueryTabs.Count then
Result := QueryTabs[PageIndex].GridResult
else
Result := nil;
end;
function TMainForm.QueryTabActive: Boolean;
begin
// Find out if the active main tab is a query tab
Result := IsQueryTab(PageControlMain.ActivePageIndex, True);
end;
function TMainForm.IsQueryTab(PageIndex: Integer; IncludeFixed: Boolean): Boolean;
var
Min: Integer;
begin
// Find out if the given main tab is a query tab
Min := tabQuery.PageIndex+1;
if IncludeFixed then Dec(Min);
Result := PageIndex >= Min;
end;
procedure TMainForm.SetWindowCaption;
var
Cap: String;
begin
// Set window caption and taskbar text
Cap := SessionName;
if ActiveDatabase <> '' then
Cap := Cap + ' /' + ActiveDatabase;
if SelectedTable.Name <> '' then
Cap := Cap + '/' + SelectedTable.Name;
Cap := Cap + ' - ' + APPNAME;
if PortableMode then
Cap := Cap + ' Portable';
Cap := Cap + ' ' + AppVersion;
Caption := Cap;
Application.Title := Cap;
end;
procedure TMainForm.OnMessageHandler(var Msg: TMsg; var Handled: Boolean);
begin
// Clicks on system window menu get handled here
if Msg.message = WM_SYSCOMMAND then begin
Handled := True;
case Msg.wParam of
MSG_UPDATECHECK: Mainform.actUpdateCheck.Execute;
MSG_ABOUT: Mainform.actAboutBox.Execute;
else Handled := False;
end;
end;
end;
procedure TMainForm.SetTabCaption(PageIndex: Integer; Text: String);
begin
if PageIndex >= PageControlMain.PageCount then begin
// The current tab can be closed already if we're here after CloseQueryTab()
Exit;
end;
// Special case if passed text is empty: Reset query tab caption to "Query #123"
if (PageIndex = tabQuery.PageIndex) and (Text = '') then
Text := 'Query';
if IsQueryTab(PageIndex, False) then begin
if Text = '' then
Text := 'Query #'+IntToStr(PageIndex-tabQuery.PageIndex);
// Leave space for close button on closable query tabs
Text := Text + ' ';
end;
TTabSheet(PageControlMain.Pages[PageIndex]).Caption := Text;
FixQueryTabCloseButtons;
end;
function TMainForm.ConfirmTabClose(PageIndex: Integer): Boolean;
var
msg: String;
Tab: TQueryTab;
begin
Tab := QueryTabs[PageIndex-tabQuery.PageIndex];
if not Tab.Memo.Modified then
Result := True
else begin
// Unhide tabsheet so the user sees the memo content
Tab.TabSheet.PageControl.ActivePage := Tab.TabSheet;
if Tab.MemoFilename <> '' then
msg := 'Save changes to file '+CRLF+CRLF+Tab.MemoFilename+' ?'
else
msg := 'Save content of tab "'+Trim(Tab.TabSheet.Caption)+'" ?';
case MessageDlg(msg, mtConfirmation, [mbYes, mbNo, mbCancel], 0) of
mrNo: Result := True;
mrYes: begin
if Tab.MemoFilename <> '' then
SaveQueryMemo(Tab, Tab.MemoFilename, False)
else if SaveDialogSQLFile.Execute then
SaveQueryMemo(Tab, SaveDialogSQLFile.FileName, False);
// The save dialog can be cancelled.
Result := not Tab.Memo.Modified;
end;
else Result := False;
end;
end;
end;
procedure TMainForm.actFilterPanelExecute(Sender: TObject);
var
MakeVisible: Boolean;
begin
// (De-)activate or focus filter panel
MakeVisible := Sender <> btnCloseFilterPanel;
pnlFilterVT.Visible := MakeVisible;
pnlFilterVT.Tag := Integer(MakeVisible);
// On startup, we cannot SetFocus, throws exceptons. Call with nil in that special case - see FormCreate
if Assigned(Sender) and MakeVisible and editFilterVT.CanFocus then
editFilterVT.SetFocus;
end;
procedure TMainForm.UpdateFilterPanel(Sender: TObject);
var
tab: TTabSheet;
f: String;
FilterPanelVisible: Boolean;
begin
// Called when active tab changes
pnlFilterVT.Enabled := PageControlMain.ActivePage <> tabEditor;
lblFilterVT.Enabled := pnlFilterVT.Enabled;
editFilterVT.Enabled := pnlFilterVT.Enabled;
lblFilterVTInfo.Enabled := pnlFilterVT.Enabled;
if pnlFilterVT.Enabled then
editFilterVT.Color := clWindow
else
editFilterVT.Color := clBtnFace;
tab := PageControlMain.ActivePage;
if tab = tabHost then
tab := PageControlHost.ActivePage;
FilterPanelVisible := pnlFilterVT.Tag = Integer(True);
if not FilterPanelVisible then begin
if editFilterVT.Text <> '' then
editFilterVT.Text := ''
else
editFilterVTChange(Sender);
end else begin
if tab = tabVariables then f := FilterTextVariables
else if tab = tabStatus then f := FilterTextStatus
else if tab = tabProcesslist then f := FilterTextProcessList
else if tab = tabCommandStats then f := FilterTextCommandStats
else if tab = tabDatabase then f := FilterTextDatabase
else if tab = tabData then f := FilterTextData
else if QueryTabActive and (tab = ActiveQueryTab.TabSheet) then f := ActiveQueryTab.FilterText;
if editFilterVT.Text <> f then
editFilterVT.Text := f
end;
end;
procedure TMainform.SetupSynEditors;
var
i, j: Integer;
Editors: TObjectList;
BaseEditor, Editor: TSynMemo;
FontName: String;
FontSize, TabWidth: Integer;
KeyStroke: TSynEditKeyStroke;
ActiveLineColor: TColor;
Attri: TSynHighlighterAttributes;
Shortcut1, Shortcut2: TShortcut;
procedure FindEditors(Comp: TComponent);
var i: Integer;
begin
for i:=0 to Comp.ComponentCount-1 do begin
if Comp.Components[i] is TSynMemo then
Editors.Add(Comp.Components[i]);
FindEditors(Comp.Components[i]);
end;
end;
begin
// Restore font, highlighter and shortcuts for each instantiated TSynMemo
Editors := TObjectList.Create;
BaseEditor := SynMemoQuery;
for i:=0 to QueryTabs.Count-1 do
Editors.Add(QueryTabs[i].Memo);
Editors.Add(SynMemoFilter);
Editors.Add(SynMemoProcessView);
Editors.Add(SynMemoSQLLog);
if Assigned(ActiveObjectEditor) then
FindEditors(ActiveObjectEditor);
if Assigned(CreateDatabaseForm) then
Editors.Add(CreateDatabaseForm.SynMemoPreview);
if Assigned(OptionsForm) then
Editors.Add(OptionsForm.SynMemoSQLSample);
if Assigned(SQLHelpForm) then begin
Editors.Add(SQLHelpForm.memoDescription);
Editors.Add(SQLHelpForm.MemoExample);
end;
FontName := GetRegValue(REGNAME_FONTNAME, DEFAULT_FONTNAME);
FontSize := GetRegValue(REGNAME_FONTSIZE, DEFAULT_FONTSIZE);
TabWidth := GetRegValue(REGNAME_TABWIDTH, DEFAULT_TABWIDTH);
ActiveLineColor := StringToColor(GetRegValue(REGNAME_SQLCOLACTIVELINE, ColorToString(DEFAULT_SQLCOLACTIVELINE)));
for i:=0 to Editors.Count-1 do begin
Editor := Editors[i] as TSynMemo;
Editor.Font.Name := FontName;
Editor.Font.Size := FontSize;
Editor.Gutter.Font.Name := FontName;
Editor.Gutter.Font.Size := FontSize;
Editor.Gutter.AutoSize := BaseEditor.Gutter.AutoSize;
Editor.Gutter.DigitCount := BaseEditor.Gutter.DigitCount;
Editor.Gutter.LeftOffset := BaseEditor.Gutter.LeftOffset;
Editor.Gutter.RightOffset := BaseEditor.Gutter.RightOffset;
Editor.Gutter.ShowLineNumbers := BaseEditor.Gutter.ShowLineNumbers;
Editor.ActiveLineColor := ActiveLineColor;
Editor.Options := BaseEditor.Options;
Editor.TabWidth := TabWidth;
Editor.MaxScrollWidth := BaseEditor.MaxScrollWidth;
Editor.WantTabs := BaseEditor.WantTabs;
Editor.OnPaintTransient := BaseEditor.OnPaintTransient;
// Shortcuts
if Editor = BaseEditor then for j:=0 to Editor.Keystrokes.Count-1 do begin
KeyStroke := Editor.Keystrokes[j];
Shortcut1 := GetRegValue(REGPREFIX_SHORTCUT1+EditorCommandToCodeString(Keystroke.Command), KeyStroke.ShortCut);
Shortcut2 := GetRegValue(REGPREFIX_SHORTCUT2+EditorCommandToCodeString(Keystroke.Command), KeyStroke.ShortCut2);
try
Keystroke.ShortCut := Shortcut1;
Keystroke.ShortCut2 := Shortcut2;
except
on E:ESynKeyError do begin
LogSQL('Could not apply SynEdit keystroke shortcut "'+ShortCutToText(Shortcut1)+'"' +
' (or secondary: "'+ShortCutToText(Shortcut2)+'") to '+EditorCommandToCodeString(Keystroke.Command)+'. '+
E.Message + '. Please go to Tools > Preferences > Shortcuts to change this settings.', lcError);
end;
end;
end else
Editor.Keystrokes := BaseEditor.KeyStrokes;
end;
// Highlighting
for i:=0 to SynSQLSyn1.AttrCount - 1 do begin
Attri := SynSQLSyn1.Attribute[i];
Attri.Foreground := GetRegValue(REGPREFIX_SQLATTRI+Attri.FriendlyName+REGPOSTFIX_SQL_FG, Attri.Foreground);
Attri.Background := GetRegValue(REGPREFIX_SQLATTRI+Attri.FriendlyName+REGPOSTFIX_SQL_BG, Attri.Background);
Attri.IntegerStyle := GetRegValue(REGPREFIX_SQLATTRI+Attri.FriendlyName+REGPOSTFIX_SQL_STYLE, Attri.IntegerStyle);
if Assigned(OptionsForm) then
OptionsForm.SynSQLSynSQLSample.Attribute[i].AssignColorAndStyle(Attri);
end;
end;
procedure TMainForm.actReformatSQLExecute(Sender: TObject);
var
Control: TWinControl;
m: TCustomSynEdit;
CursorPosStart, CursorPosEnd: Integer;
NewSQL: String;
begin
// Reformat SQL query
m := nil;
Control := Screen.ActiveControl;
if Control is TCustomSynEdit then begin
m := Control as TCustomSynEdit;
// We have a few readonly-SynMemos which we'll ignore here
if m.ReadOnly then
m := nil;
end;
if (not Assigned(m)) and QueryTabActive then
m := ActiveQueryMemo;
if not Assigned(m) then begin
MessageDlg('Please select a non-readonly SQL editor first.', mtError, [mbOK], 0);
Exit;
end;
CursorPosStart := m.SelStart;
CursorPosEnd := m.SelEnd;
if not m.SelAvail then
m.SelectAll;
NewSQL := m.SelText;
if Length(NewSQL) = 0 then
MessageDlg('Cannot reformat anything - your editor is empty.', mtError, [mbOK], 0)
else begin
Screen.Cursor := crHourglass;
m.UndoList.AddGroupBreak;
NewSQL := ReformatSQL(NewSQL);
m.SelText := NewSQL;
m.SelStart := CursorPosStart;
if CursorPosEnd > CursorPosStart then
m.SelEnd := CursorPosStart + Length(NewSQL);
m.UndoList.AddGroupBreak;
Screen.Cursor := crDefault;
end;
end;
procedure TMainForm.PageControlMainContextPopup(Sender: TObject; MousePos: TPoint; var Handled: Boolean);
var
ClickPoint: TPoint;
TabsHeight: Integer;
begin
// Activate tab popup menu only when clicked on tabs area.
TabsHeight := (btnAddTab.Height+2) * PageControlMain.RowCount;
if MousePos.Y <= TabsHeight then begin
ClickPoint := PageControlMain.ClientToScreen(MousePos);
popupMainTabs.Popup(ClickPoint.X, ClickPoint.Y);
Handled := True;
end else
Handled := False;
end;
procedure TMainForm.menuQueryHelpersGenerateStatementClick(Sender: TObject);
var
MenuItem: TMenuItem;
sql, Val, WhereClause: String;
i, idx: Integer;
ColumnNames, DefaultValues, KeyColumns: TStringList;
Column: TTableColumn;
begin
// Generate INSERT, UPDATE or DELETE query using selected columns
MenuItem := (Sender as TMenuItem);
ColumnNames := TStringList.Create;
DefaultValues := TStringList.Create;
for i:=0 to ActiveQueryHelpers.Items.Count-1 do begin
if ActiveQueryHelpers.Selected[i] then begin
ColumnNames.Add(mask(ActiveQueryHelpers.Items[i]));
Column := SelectedTableColumns[i];
case Column.DataType.Category of
dtcInteger, dtcReal: Val := '0';
dtcText, dtcIntegerNamed, dtcSetNamed: begin
Val := esc(Column.DefaultText);
if Column.DefaultType in [cdtNull, cdtNullUpdateTS] then
Val := esc('')
else
Val := esc(Column.DefaultText);
end;
dtcTemporal: Val := 'NOW()';
else Val := 'NULL';
end;
if Column.DefaultType = cdtAutoInc then
Val := 'NULL';
DefaultValues.Add(Val);
end;
end;
KeyColumns := GetKeyColumns;
if KeyColumns.Count > 0 then begin
WhereClause := '';
for i:=0 to KeyColumns.Count-1 do begin
idx := ColumnNames.IndexOf(mask(KeyColumns[i]));
if idx > -1 then
WhereClause := WhereClause + mask(KeyColumns[i])+'='+DefaultValues[idx] + ' AND ';
end;
Delete(WhereClause, Length(sql)-3, 4);
end else
WhereClause := '??? # No primary or unique key available!';
if MenuItem = menuQueryHelpersGenerateInsert then begin
sql := 'INSERT INTO '+mask(SelectedTable.Name)+CRLF+
#9'('+ImplodeStr(', ', ColumnNames)+')'+CRLF+
#9'VALUES ('+ImplodeStr(', ', DefaultValues)+')';
end else if MenuItem = menuQueryHelpersGenerateUpdate then begin
sql := 'UPDATE '+mask(SelectedTable.Name)+CRLF+#9'SET'+CRLF;
if ColumnNames.Count > 0 then begin
for i:=0 to ColumnNames.Count-1 do begin
sql := sql + #9#9 + ColumnNames[i] + '=' + DefaultValues[i] + ',' + CRLF;
end;
Delete(sql, Length(sql)-2, 1);
end else
sql := sql + #9#9'??? # No column names selected!'+CRLF;
sql := sql + #9'WHERE ' + WhereClause;
end else if MenuItem = menuQueryHelpersGenerateDelete then begin
sql := 'DELETE FROM '+mask(SelectedTable.Name)+' WHERE ' + WhereClause;
end;
ActiveQueryMemo.UndoList.AddGroupBreak;
ActiveQueryMemo.SelText := sql;
end;
procedure TMainForm.DBtreeChange(Sender: TBaseVirtualTree; Node: PVirtualNode);
var
VT: TVirtualStringTree;
begin
// Resize "Size" column in dbtree to hold widest possible byte numbers without cutting text
VT := Sender as TVirtualStringTree;
if coVisible in VT.Header.Columns[1].Options then
VT.Header.Columns[1].Width := TextWidth(VT.Canvas, FormatByteNumber(SIZE_MB-1))+VT.TextMargin*2;
end;
procedure TMainForm.FormMouseWheel(Sender: TObject; Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint;
var Handled: Boolean);
var
Control: TWinControl;
VT: TBaseVirtualTree;
begin
// Wheel scrolling only works in component which has focus. Help out by doing that by hand at least for any VirtualTree.
// See http://www.delphipraxis.net/viewtopic.php?p=1113607
// TODO: Does not work when a SynMemo has focus, probably related to the broken solution of this issue:
// http://sourceforge.net/tracker/index.php?func=detail&aid=1574059&group_id=3221&atid=103221
Control := FindVCLWindow(MousePos);
if (Control is TBaseVirtualTree) and (not Control.Focused) and PtInRect(Control.ClientRect, Control.ScreenToClient(MousePos)) then begin
VT := Control as TBaseVirtualTree;
VT.OffsetY := VT.OffsetY + (WheelDelta div 2); // Don't know why, but WheelDelta is twice as big as it normally appears
VT.UpdateScrollBars(True);
Handled := True;
end else
Handled := False;
end;
procedure TMainForm.actDataResetSortingExecute(Sender: TObject);
begin
SetLength(DataGridSortColumns, 0);
InvalidateVT(DataGrid, VTREE_NOTLOADED_PURGECACHE, False);
end;
procedure TMainForm.WMCopyData(var Msg: TWMCopyData);
var
i: Integer;
begin
// Probably a second instance is posting its command line parameters here
if (Msg.CopyDataStruct.dwData = SecondInstMsgId) and (SecondInstMsgId <> 0) then begin
ParseCommandLineParameters(ParamBlobToStr(Msg.CopyDataStruct.lpData));
for i:=0 to FCmdlineFilenames.Count-1 do begin
actNewQueryTabExecute(self);
if not QueryLoad(FCmdlineFilenames[i]) then
actCloseQueryTabExecute(Self);
end;
if Assigned(FCmdlineConnectionParams) then
if InitConnection(FCmdlineConnectionParams, FCmdlineSessionName) then
DoAfterConnect;
end else
// Not the right message id
inherited;
end;
procedure TMainForm.DefaultHandler(var Message);
begin
if TMessage(Message).Msg = SecondInstMsgId then begin
// A second instance asked for our handle. Post that into its message queue.
PostThreadMessage(TMessage(Message).WParam, SecondInstMsgId, Handle, 0);
end else
// Otherwise do what would happen without this overridden procedure
inherited;
end;
procedure TMainForm.actBlobAsTextExecute(Sender: TObject);
begin
// Activate displaying BLOBs as text data, ignoring possible weird effects in grid updates/inserts
InvalidateVT(DataGrid, VTREE_NOTLOADED_PURGECACHE, False);
end;
function TMainForm.GetBlobContent(Results: TMySQLQuery; Column: Integer): String;
begin
if actBlobAsText.Checked then
Result := Results.Col(Column)
else
Result := '0x' + Results.BinColAsHex(Column);
end;
procedure TMainForm.vstScroll(Sender: TBaseVirtualTree; DeltaX, DeltaY: Integer);
begin
// A tree gets scrolled only when the mouse is over it - see FormMouseWheel
// Our home brewn cell editors do not reposition when the underlying tree scrolls.
// To avoid confusion, terminate editors then.
if Sender.IsEditing then
Sender.EndEditNode;
end;
procedure TMainForm.lblExplainProcessClick(Sender: TObject);
var
Tab: TQueryTab;
begin
// Click on "Explain" link label, in process viewer
actNewQueryTabExecute(Sender);
Tab := QueryTabs[QueryTabs.Count-1];
Tab.Memo.Text := 'USE '+mask(listProcesses.Text[listProcesses.FocusedNode, 3])+';'+CRLF+
'EXPLAIN'+CRLF+SynMemoProcessView.Text;
Tab.TabSheet.Show;
actExecuteQueryExecute(Sender);
end;
procedure TMainForm.UpdateLineCharPanel;
var
x, y: Int64;
Grid: TVirtualStringTree;
begin
// Fill panel with "Line:Char"
x := -1;
y := -1;
Grid := ActiveGrid;
if Assigned(Grid) and Grid.Focused then begin
if Assigned(Grid.FocusedNode) then
y := Grid.FocusedNode.Index+1;
x := Grid.FocusedColumn+1;
end else if QueryTabActive and ActiveQueryMemo.Focused then begin
x := ActiveQueryMemo.CaretX;
y := ActiveQueryMemo.CaretY;
end;
if (x > -1) and (y > -1) then
ShowStatusMsg(FormatNumber(y)+' : '+FormatNumber(x), 1)
else
ShowStatusMsg('', 1);
end;
end.