MeseconEdit/MeseconEdit.ahk

472 lines
14 KiB
Plaintext

#NoEnv
;wip: multiple simultaneous viewports with independent views
;wip: undo/redo
;wip: component count in status bar - nodes used in selection, in total
;wip: rectangular selection and selection filling/moving/copying/pasting
/*
Copyright 2012 Anthony Zhang <azhang9@gmail.com>
This file is part of MeseconEdit. Source code is available at <https://github.com/Uberi/MeseconEdit>.
MeseconEdit is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#Warn All
#Warn LocalSameAsGlobal, Off
Width := 800
Height := 600
CurrentFile := ""
Tools := []
Tools.Insert(Object("Name","&Draw", "Class",ToolActions.Draw))
Tools.Insert(Object("Name","&Remove", "Class",ToolActions.Remove))
Tools.Insert(Object("Name","&Select", "Class",ToolActions.Select))
Tools.Insert(Object("Name","&Actuate","Class",ToolActions.Actuate))
Menu, FileMenu, Add, &New, FileNew
Menu, FileMenu, Add, &Open, FileOpen
Menu, FileMenu, Add
Menu, FileMenu, Add, &Save, FileSave
Menu, FileMenu, Add, Save &As, FileSaveAs
Menu, FileMenu, Add
Menu, FileMenu, Add, E&xit, MainGuiClose
;Menu, OptionsMenu, Add, &Simulation, ShowSimulationOptions
Menu, HelpMenu, Add, &Manual, ShowHelp
Menu, HelpMenu, Add, &About, ShowAbout
Menu, MenuBar, Add, &File, :FileMenu
;Menu, MenuBar, Add, &Options, :OptionsMenu
Menu, MenuBar, Add, &Help, :HelpMenu
Gui, Main:Menu, MenuBar
Gui, Main:Add, Text, vDisplay gDisplayClick hwndhControl
InitializeViewport(hControl,Width,Height)
For Index, Tool In Tools
Gui, Main:Add, Radio, w80 h20 vTool%Index% gSelectTool, % Tool.Name
GuiControl, Main:, Tool1, 1
Gui, Main:Add, ListBox, w80 h200 vSubtools
Gui, Main:+Resize +MinSize400x320
Gui, Main:Show, w800 h600 Hide
Gosub, FileNew
CurrentTool := Tools[1]
CurrentTool.Class.Select()
Gosub, Draw
SetTimer, Draw, 50
Return
class ToolActions
{
#Include Tools\Draw.ahk
#Include Tools\Remove.ahk
#Include Tools\Select.ahk
#Include Tools\Actuate.ahk
}
class Nodes
{
#Include %A_ScriptDir%\Nodes\Basis.ahk
#Include %A_ScriptDir%\Nodes\Mesecon.ahk
#Include %A_ScriptDir%\Nodes\Power Plant.ahk
#Include %A_ScriptDir%\Nodes\Blinky Plant.ahk
#Include %A_ScriptDir%\Nodes\Meselamp.ahk
#Include %A_ScriptDir%\Nodes\Plug.ahk
#Include %A_ScriptDir%\Nodes\Socket.ahk
#Include %A_ScriptDir%\Nodes\Inverter.ahk
}
FileNew:
Grid := []
Viewport := Object("X",-14.5,"Y",-14.5,"W",30,"H",30)
Gui, Main:Show,, MeseconEdit - Untitled
ResizeWindow(Width,Height)
Return
FileOpen:
FileSelectFile, FileName, 35,, Open mesecon schematic, Mesecon Schematic (*.mesecon)
If ErrorLevel
Return
CurrentFile := FileName
FileRead, Value, %CurrentFile%
If ErrorLevel
{
MsgBox, 16, Error, Could not read file "%FileName%".
Return
}
;wip: ask to save current file if modified
Gui, Main:Show,, MeseconEdit - %CurrentFile%
Grid := Deserialize(Value)
Return
FileSave:
;wip: don't save file if not modified
If (CurrentFile = "")
{
Gosub, FileSaveAs
Return
}
FileDelete, %CurrentFile%
FileAppend, % Serialize(Grid), %CurrentFile%
If ErrorLevel
{
Gui, Main:+OwnDialogs
MsgBox, 16, Error, Could not save file as "%CurrentFile%".
}
Return
FileSaveAs:
FileSelectFile, FileName, S48,, Save mesecon schematic, Mesecon Schematic (*.mesecon)
If ErrorLevel
Return
CurrentFile := FileName
Gui, Main:Show,, MeseconEdit - %CurrentFile%
Gosub, FileSave
Return
ShowHelp:
;wip: open manual here
Return
ShowAbout:
Gui, Main:+Disabled
Gui, About:+OwnerMain +ToolWindow
Gui, About:Font, s48, Arial
Gui, About:Add, Text, x10 y10 w400 h70, MeseconEdit
Gui, About:Font, s8 Bold
Gui, About:Add, Text, x10 y80 w200 h20, v1.0 Stable
Gui, About:Font, Norm
Gui, About:Add, Text, x210 y80 w200 h20 Right, Copyright Anthony Zhang 2012
Gui, About:Font, s12
Gui, About:Add, Link, x10 y110 w400 h20, Licensed under the <a href="http://www.gnu.org/licenses/">GNU Affero General Public License</a>.
Gui, About:Show, w420 h140
Return
AboutGuiEscape:
AboutGuiClose:
Gui, About:Destroy
Gui, Main:-Disabled
Gui, Main:Show
Return
MainGuiClose:
SetTimer, Draw, Off
UninitializeViewport(hControl)
ExitApp
MainGuiSize:
Critical
If A_EventInfo = 1 ;window minimised
Return
Width := A_GuiWidth
Height := A_GuiHeight
ResizeWindow(Width,Height)
Sleep, 10
Return
SelectTool:
;store the index of the previously selected subtool
Gui, Main:+LastFound
SendMessage, 0x188, 0, 0, ListBox1 ;LB_GETCURSEL
CurrentTool.Class.Subtool := ErrorLevel + 1
;select the current tool
CurrentTool := Tools[SubStr(A_GuiControl,5)]
CurrentTool.Class.Select()
Return
Draw:
Draw(Grid,Width,Height,Viewport)
Return
#IfWinActive MeseconEdit ahk_class AutoHotkeyGUI
~RButton::
CoordMode, Mouse, Client
MouseGetPos, OffsetX, OffsetY
ViewportX1 := Viewport.X, ViewportY1 := Viewport.Y
While, GetKeyState("RButton","P")
{
MouseGetPos, MouseX, MouseY
PositionX := MouseX - OffsetX
PositionY := MouseY - OffsetY
Viewport.X := ViewportX1 - ((PositionX / Width) * Viewport.W)
Viewport.Y := ViewportY1 - ((PositionY / Height) * Viewport.H)
;obtain the position of the viewport
GuiControlGet, TempPosition, Main:Pos, Display
If (MouseX < TempPositionX) ;mouse past left edge of viewport
{
OffsetX += TempPositionW
MouseMove, TempPositionX + TempPositionW, MouseY, 0
}
Else If (MouseX > TempPositionX + TempPositionW) ;mouse past right edge of viewport
{
OffsetX -= TempPositionW
MouseMove, TempPositionX, MouseY, 0
}
If (MouseY < TempPositionY) ;mouse past top edge of viewport
{
OffsetY += TempPositionH
MouseMove, MouseX, TempPositionY + TempPositionH, 0
}
Else If (MouseY > TempPositionY + TempPositionH) ;mouse past bottom edge of viewport
{
OffsetY -= TempPositionH
MouseMove, MouseX, TempPositionY, 0
}
Sleep, 50
}
Return
Space::
While, GetKeyState("Space","P")
{
GetMouseCoordinates(Width,Height,MouseX,MouseY)
Node := Grid[MouseX][MouseY]
If Node
ToolTip % "Type: " . Node.__Class . "`nState: " . Node.State
Else
ToolTip
Sleep, 100
}
Return
DisplayClick:
Gui, Main:Submit, NoHide
For Index, Tool In Tools
{
If Tool%Index%
{
Tool.Class.Activate(Grid)
Break
}
}
Return
~PGUP::
~WheelUp::
If Viewport.W > 2
{
Viewport.X += Viewport.W * 0.1, Viewport.Y += Viewport.H * 0.1
Viewport.W *= 0.8, Viewport.H *= 0.8
}
Return
~PGDN::
~WheelDown::
If Viewport.W < 80
{
Viewport.X -= Viewport.W * 0.1, Viewport.Y -= Viewport.H * 0.1
Viewport.W *= 1.2, Viewport.H *= 1.2
}
Return
Serialize(Grid)
{
Result := ""
For IndexX, Column In Grid
{
For IndexY, Node In Column
Result .= IndexX . "`t" . IndexY . "`t" . Node.__Class . "`t" . Node.Serialize() . "`n"
}
Return, SubStr(Result,1,-1)
}
Deserialize(Value)
{
global
local NodeClasses, Grid, Position, IndexX, IndexY, NodeName, Data, NodeClass
;create a mapping of node class names to node classes
NodeClasses := Object()
For Name, Node In Nodes
{
If IsObject(Node)
NodeClasses[Node.__Class] := Node
}
Grid := []
StringReplace, Value, Value, `r,, All
Value := Trim(Value,"`n")
Loop, Parse, Value, `n
{
;wip: serialized data cannot contain newlines
Data := A_LoopField
Position := InStr(Data,"`t")
IndexX := SubStr(Data,1,Position - 1)
Data := SubStr(Data,Position + 1)
Position := InStr(Data,"`t")
IndexY := SubStr(Data,1,Position - 1)
Data := SubStr(Data,Position + 1)
Position := InStr(Data,"`t")
NodeName := SubStr(Data,1,Position - 1)
Data := SubStr(Data,Position + 1)
If !NodeClasses.HasKey(NodeName)
throw Exception("Unknown node class: " . NodeName . ".")
Grid[IndexX,IndexY] := NodeClasses[NodeName].Deserialize(IndexX,IndexY,Data)
}
Return, Grid
}
GetMouseCoordinates(Width,Height,ByRef MouseX,ByRef MouseY)
{
global Viewport
;obtain the mouse position
CoordMode, Mouse, Client
MouseGetPos, MouseX, MouseY
;obtain the viewport position
GuiControlGet, Offset, Main:Pos, Display
;calculate the cell the mouse in in
MouseX -= OffsetX, MouseY -= OffsetY
MouseX := Floor(Viewport.X + ((MouseX / Width) * Viewport.W))
MouseY := Floor(Viewport.Y + ((MouseY / Height) * Viewport.H))
}
InitializeViewport(hWindow,Width,Height)
{
global hDC, hMemoryDC, hOriginalBitmap
hDC := DllCall("GetDC","UPtr",hWindow)
If !hDC
throw Exception("Could not obtain window device context.")
hMemoryDC := DllCall("CreateCompatibleDC","UPtr",hDC)
If !hMemoryDC
throw Exception("Could not obtain window device context.")
hOriginalBitmap := 0
ResizeViewport(Width,Height)
}
UninitializeViewport(hWindow)
{
global hDC, hMemoryDC, hBitmap
If hBitmap && !DllCall("DeleteObject","UPtr",hBitmap) ;delete the bitmap
throw Exception("Could not delete bitmap.")
If !DllCall("DeleteObject","UPtr",hMemoryDC) ;delete the memory device context
throw Exception("Could not delete memory device context.")
If !DllCall("ReleaseDC","UPtr",hWindow,"UPtr",hDC) ;release the window device context
throw Exception("Could not release window device context.")
}
Draw(Grid,Width,Height,Viewport)
{
global hDC, hMemoryDC
static hPen := DllCall("CreatePen","Int",0,"Int",0,"UInt",0x888888,"UPtr") ;PS_SOLID
;clear the bitmap
If !DllCall("BitBlt","UPtr",hMemoryDC,"Int",0,"Int",0,"Int",Width,"Int",Height,"UPtr",hMemoryDC,"Int",0,"Int",0,"UInt",0x42) ;BLACKNESS
throw Exception("Could not transfer pixel data to window device context.")
;determine the dimensions of each cell
BlockW := Width / Viewport.W, BlockH := Height / Viewport.H
IndexX := Floor(Viewport.X), IndexY := Floor(Viewport.Y)
;determine the horizontal position of the first cell
BlockX := Mod(-Viewport.X,1) * BlockW
If BlockX > 0
BlockX -= BlockW
;determine the vertical position of the first cell
BlockY := Mod(-Viewport.Y,1) * BlockH
If BlockY > 0
BlockY -= BlockH
;draw grid lines
IndexX1 := IndexX, BlockX1 := BlockX
hOriginalPen := DllCall("SelectObject","UPtr",hMemoryDC,"UPtr",hPen,"UPtr") ;select the pen
Loop, % Ceil(Viewport.W)
{
BlockX1 += BlockW
DllCall("MoveToEx","UPtr",hMemoryDC,"Int",BlockX1,"Int",0,"UPtr",0)
DllCall("LineTo","UPtr",hMemoryDC,"Int",BlockX1,"Int",Height)
}
IndexY1 := IndexY, BlockY1 := BlockY
Loop, % Ceil(Viewport.H)
{
BlockY1 += BlockH
DllCall("MoveToEx","UPtr",hMemoryDC,"Int",0,"Int",BlockY1,"UPtr",0)
DllCall("LineTo","UPtr",hMemoryDC,"Int",Width,"Int",BlockY1)
}
DllCall("SelectObject","UPtr",hMemoryDC,"UPtr",hOriginalPen,"UPtr") ;deselect the pen
;draw cells
Loop, % Ceil(Viewport.W) + 1
{
IndexY1 := IndexY, BlockY1 := BlockY
Loop, % Ceil(Viewport.H) + 1
{
If Grid[IndexX,IndexY1]
Grid[IndexX,IndexY1].Draw(BlockX,BlockY1,BlockW,BlockH)
IndexY1 ++, BlockY1 += BlockH
}
IndexX ++, BlockX += BlockW
}
;transfer pixel data to window device context
If !DllCall("BitBlt","UPtr",hDC,"Int",0,"Int",0,"Int",Width,"Int",Height,"UPtr",hMemoryDC,"Int",0,"Int",0,"UInt",0xCC0020) ;SRCCOPY
throw Exception("Could not transfer pixel data to window device context.")
}
ResizeWindow(Width,Height)
{
global Viewport, Tools
ViewportWidth := Width - 110, ViewportHeight := Height - 20
GuiControl, Main:Move, Display, x10 y10 w%ViewportWidth% h%ViewportHeight%
ResizeViewport(ViewportWidth,ViewportHeight)
Viewport.Y += Viewport.H / 2
Viewport.H := (Height / Width) * Viewport.W
Viewport.Y -= Viewport.H / 2
Temp1 := 10
For Index In Tools
GuiControl, Main:Move, Tool%Index%, % "x" . (Width - 90) . " y" . Temp1, Temp1 += 20
Temp1 += 20
GuiControl, Main:Move, Subtools, % "x" . (Width - 90) . " y" . Temp1
}
ResizeViewport(Width,Height)
{
global hDC, hMemoryDC, hOriginalBitmap, hBitmap
If hOriginalBitmap
{
If !DllCall("SelectObject","UPtr",hMemoryDC,"UPtr",hOriginalBitmap,"UPtr") ;deselect the bitmap
throw Exception("Could not select original bitmap into memory device context.")
}
hBitmap := DllCall("CreateCompatibleBitmap","UPtr",hDC,"Int",Width,"Int",Height,"UPtr") ;create a new bitmap
If !hBitmap
throw Exception("Could not create bitmap.")
hOriginalBitmap := DllCall("SelectObject","UPtr",hMemoryDC,"UPtr",hBitmap,"UPtr")
If !hOriginalBitmap
throw Exception("Could not select bitmap into memory device context.")
}